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>
- Auto-stop : notification 10 min avant, arrêt automatique après MAX_SESSION_DURATION_S (1h)
- Lea.bat : kill des anciens process (python, pythonw, rpa-agent) au démarrage
- LISEZMOI : simplifié pour les collaborateurs (pas de replay, juste collecte)
- Chat server (5004) vérifié fonctionnel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Screenshots de référence (res_shot_XXXX.png) attachés aux actions click/key_combo
- _attach_expected_screenshots() charge les screenshots résultat de l'enregistrement
- _verify_visual_state() dans executor : 2 étages de vérification
- Étage 1 : template matching rapide (~100ms), score > 0.7 = OK, < 0.3 = FAIL
- Étage 2 : VLM compare current vs expected (~4s), MATCH/MISMATCH
- Résultat attaché à chaque action (visual_verification dans result)
- Note : executor sur Windows (/tmp/executor_win.py) à synchroniser manuellement
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Popup handling via double appel VLM (détection + localisation précise du bouton)
- Reconstruction texte depuis raw_keys (numpad /, @ AltGr fusionné)
- Clipboard paste pour texte riche, raw_keys pour commandes simples (Win+R)
- Skip des release orphelins dans raw_keys (fix menu Démarrer parasite)
- Auth Bearer sur toutes les requêtes agent → streaming server
- Endpoints /replay/next et /stream/image publics (agent Rust legacy)
- alt_gr ajouté dans _MODIFIER_ONLY_KEYS
- _key_combo_printable_char détecte ctrl+@ comme caractère imprimable
- start.bat tue les anciens process (python + rpa-agent) au démarrage
- Heartbeat avec token Bearer dans main.py et deploy/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Worker VLM séparé :
- run_worker.py : process distinct du serveur HTTP
- Communication par fichiers (_worker_queue.txt + _replay_active.lock)
- Service systemd rpa-worker.service
- Le serveur HTTP ne charge plus CLIP/VLM (mode léger)
- StreamProcessor._ensure_initialized() désactivé dans le serveur
VLM direct depuis l'agent :
- L'agent appelle Ollama directement (port 11434, LAN)
- Ollama configuré sur 0.0.0.0 (OLLAMA_HOST)
- Pas de passage par le serveur streaming (évite le blocage GIL)
- Fallback serveur supprimé (VLM direct ou STOP)
Popup handler hybride :
- VLM identifie le bouton ("Oui", "OK") — pas de coordonnées
- Template matching localise le texte sur l'écran (PIL + cv2)
- _find_text_on_screen() : rend le texte en image, matchTemplate
- _vlm_identify_popup_button() : prompt simple, prefill texte
Resolve visuel hybride :
- Cascade : template anchor → VLM+template texte → VLM direct (legacy)
- _hybrid_vlm_resolve() : VLM identifie + template localise
- _template_match_anchor() : match direct crop, seuil 0.80
- Seuil strict 0.90 pour template matching en mode replay
Analyse VLM temps réel désactivée :
- process_screenshot() ne fait plus de VLM (stockage uniquement)
- L'analyse est différée au worker séparé
- Le serveur HTTP reste réactif en permanence
VLM prefill fix :
- num_ctx augmenté (2048 → 8192 pour images 1080p)
- bbox_2d au lieu de click_point (plus fiable)
- Coordonnées 0-1000 (format natif qwen3-vl)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analyse VLM :
- 1 seul appel VLM par screenshot au lieu de 30 (~15s vs 6.5min)
- Sélection screenshots par hash perceptuel (3-4 utiles sur 12)
- Fallback classification individuelle si appel unique échoue
- Estimation : ~1min par workflow au lieu de 78min
Rust agent :
- Léa (Edge mode app) s'ouvre automatiquement au démarrage
- Plus besoin de systray pour lancer le chat
- Fix URL chat /chat → /
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 30 crops suffisent pour les éléments UI principaux
- ~6min/screenshot au lieu de 17min (3x plus rapide)
- Bridge cherche aussi dans live_sessions/workflows/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Workflows appris par Léa visibles dans le VWB ("Appris par Léa")
- Bouton "Importer" pour éditer un workflow appris
- Bouton "Exporter pour Léa" pour rendre un workflow VWB exécutable
- Conversion bidirectionnelle core ↔ VWB via learned_workflow_bridge
- Liste unifiée dans le chat Léa (merged + dédupliquée)
- reload_workflows() sur le streaming server (pas de redémarrage)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- visual.rs : resolve via POST /replay/resolve_target
- executor.rs : resolve avant chaque clic si visual_mode=true
- Fallback blind si matching échoue
- Binaire toujours 1.8 MB (pas de nouvelle dépendance)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1527 lignes Rust, compile sans warnings, testé sur Linux.
- Capture d'écran (xcap) + JPEG base64 + hash dedup
- Heartbeat toutes les 5s vers streaming server
- Poll replay + exécution actions (clic, frappe, combos)
- Serveur HTTP port 5006 (capture, health, file-action)
- Compatible avec le streaming server Python existant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Léa se présente comme "assistante basée sur l'intelligence artificielle"
- Dialog consentement avant enregistrement (capture écran/clavier)
- Rétention logs 180 jours (Article 12 + 26(6))
- Bouton ARRÊT D'URGENCE toujours visible (Article 14)
- Transparence mode autonome explicite (Article 50)
- Rapport conformité AI Act en français (docs/CONFORMITE_AI_ACT.md)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>