Modale fullscreen au clic sur les diagrammes BPMN/DFG.
Fermeture par clic ou Échap. Les images sont illisibles en miniature.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Point de sauvegarde incluant les fichiers non committés des sessions
précédentes (systemd, docs, agents, GPU manager).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Les triggers ≤3 chars (ok, no) utilisent maintenant des frontières
de mots (\b) pour éviter les faux positifs (ok dans cookies).
Trigger "utilise des cookies" ajouté pour le pattern cookie_accept.
7/7 patterns validés en test terrain simulé.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Après la dernière étape, Léa vérifie l'écran et gère les dialogues
restants (jusqu'à 3 vérifications en cascade). Le workflow laisse
l'écran propre à la fin.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le filtre len<3 bloquait les boutons "OK" (2 chars) et "No" (2 chars).
Seuil abaissé à 2 — filtre les lettres isolées mais laisse passer
les boutons courts courants des dialogues Windows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UIPatternLibrary câblée dans l'executor et le stream processor.
Pendant un wait_for_anchor, Léa surveille l'écran toutes les secondes :
1. OCR plein écran (docTR)
2. Pattern matching (dialogues Save, OK, Cancel, cookies...)
3. OCR ciblé pour trouver le bouton par son texte réel
4. Clic sur le match le plus bas (bouton, pas titre)
Fix : seuil ratio supprimé (trigger trouvé = match, quelle que soit
la longueur du texte OCR). Matching strict mot exact ≥3 chars
(évite les faux positifs sur lettres isolées). Fallback recherche
partielle pour les lettres soulignées (E_nregistrer).
Plus aucune coordonnée hardcodée — 100% vision.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VWB Executor :
- _check_screen_for_patterns() : capture écran + OCR + pattern matching
- _handle_detected_pattern() : clic automatique sur dialogues connus
- Vérifie entre chaque étape en mode intelligent/debug
- Si un dialogue bloque (OK, Save, Cancel), Léa le gère seule
Stream Processor :
- Enrichit les ScreenState avec ui_pattern/ui_pattern_action/ui_pattern_target
- Les patterns détectés sont loggés et stockés dans les résultats
- Permet au GraphBuilder de savoir quels écrans sont des dialogues
Phase 2 du plan "connaissance native de l'environnement".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UIPatternLibrary : 16 patterns builtin (dialogues, menus, formulaires,
popups, raccourcis) qui donnent à Léa des réflexes immédiats.
Quand Léa reconnaît "Voulez-vous enregistrer ?" elle sait cliquer
sur "Enregistrer" sans apprentissage préalable.
- core/knowledge/ui_patterns.py : bibliothèque avec find_pattern(),
get_dialog_handler(), add_pattern() pour patterns appris
- Métadonnées GUI-R1 (3K exemples) extraites dans data/ (gitignored)
Phase 1 du plan "connaissance native de l'environnement".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le conteneur .fullscreen-content avait height:0 + min-height:0
qui écrasait la hauteur du flex child → image minuscule.
Le wrapper inline-block limitait aussi le dimensionnement.
Fix : overflow:hidden sans height forcée, wrapper en flex 100%.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CSS : le dropdown héritait color:white du header → forcé #212121
sur .workflow-dropdown et .dropdown-item .item-name
Bibliothèque : migration localStorage → backend (capture_library.json)
- GET/POST /api/v3/capture/library (max 50 captures)
- loadLibraryAsync() charge depuis backend, fallback localStorage
- saveLibrary() écrit dans les deux (localStorage + backend)
- capture_library.json gitignored
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le refresh du layout X11 juste avant xdotool type force le bon keymap.
Sans ça, xdotool envoie des keycodes décalés (: → M, / → !, etc.)
dans les VM spice/QEMU.
Solution trouvée via askubuntu.com/questions/914718.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remet xclip+Ctrl+V comme méthode prioritaire. Les spice tools sont
installés dans la VM → le presse-papier est partagé → pas de problème
de mapping clavier. xdotool envoie des événements synthétiques X11
que spice/QEMU traite différemment des vraies frappes (: → M).
Citrix partage aussi le clipboard nativement → même méthode en prod.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
xdotool type pour les segments alphanumériques (un seul appel, rapide),
xdotool key avec keysym uniquement pour les caractères spéciaux
(:, /, @, etc.) qui cassent en AZERTY dans les VM.
Évite le subprocess par caractère (trop lent, effet visuel désagréable).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
xdotool type envoie des scancodes QWERTY — dans une VM AZERTY,
':' devient 'M', '/' devient '!', etc.
Nouvelle approche : xdotool key avec les noms de keysym X11
(colon, slash, period, etc.) qui sont indépendants du layout.
Chaque caractère est envoyé individuellement — plus lent mais
100% fiable en AZERTY/QWERTY, local ou VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
safe_type_text() : xdotool type en priorité au lieu du presse-papier.
Le clipboard xclip ne traverse pas les VM (QEMU) ni Citrix/RDP.
xdotool envoie des frappes X11 réelles que les VM capturent.
Délai 20ms entre caractères pour fiabilité.
Cosmétique : couleur texte forcée sur les items workflow du sidebar
(color: var(--text-primary)) — était blanc sur blanc.
Logs diagnostic ajoutés dans execute_workflow_thread et execute_action.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MAX_TEMPLATE_DISTANCE dans zoned_template_match était encore à 150px.
Tous les seuils de distance sont maintenant alignés à 500px :
- MAX_DISTANCE_PX (CLIP) : 500
- MAX_GLOBAL_DISTANCE (template global) : 500
- MAX_SEECLICK_DISTANCE : 500
- MAX_TEMPLATE_DISTANCE (template zonée) : 500
Commentaires périmés corrigés (plus de références aux anciennes valeurs).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le template matching global trouvait l'icône Chrome à 0.99 de confiance
mais la rejetait car elle avait bougé de >150px. Même problème pour
SeeClick (>200px). Aligné tous les seuils de distance à 500px
pour supporter les workflows VWB cross-résolution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MAX_DISTANCE_PX 120→500 (ancre peut être loin si résolution différente)
MIN_CLIP_SCORE 0.55→0.50 (tolérance basique suffisante)
MIN_COMBINED_SCORE 0.5→0.45 (accepter les matchs raisonnables)
L'icône Chrome à 81% de confiance était rejetée à cause de la distance.
Les workflows VWB manuels capturent sur un écran et s'exécutent
potentiellement sur un autre — la tolérance de distance doit être large.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Corrige les src={{b64ImgSrc(...)}} → src={b64ImgSrc(...)} causés par
le replace_all sur les template literals. Corrige aussi l'appel
b64ImgSrc dans du code JS pur (pas de {} autour).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CSS fullscreen-content : height:0 + min-height:0 pour forcer flex fill
- Image fullscreen : max-height calc(100vh - 60px) + object-fit contain
- Fonction b64ImgSrc() détecte automatiquement PNG vs JPEG depuis le base64
- Corrige l'affichage des thumbnails compressés JPEG dans la bibliothèque
- Appliqué dans CapturePanel + CaptureLibrary (toutes les occurrences)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avant : tous les workflows importés s'appelaient « Unnamed Workflow »
→ la liste devenait illisible dès qu'il y en avait plusieurs.
Après : génération d'un nom explicite par _derive_default_name :
1. Premier `template.window.title_pattern` utile dans les nodes
(filtrage de "Unknown" / "unknown_window"), avec extraction de
l'app derrière le séparateur Windows « – » / « - »
(ex: « Sans titre – Bloc-notes » → « Bloc-notes »).
2. Premier `template.window.process_name` non-null
(ex: « explorer.exe »).
3. Fallback : 8 premiers caractères du workflow_id, après
nettoyage des préfixes techniques ("workflow_sess_", ...).
Le nom final inclut toujours la date de l'import :
« Léa Bloc-notes — 2026-04-16 08:41 »
« Léa explorer.exe — 2026-04-16 08:41 »
« Léa 20260404 — 2026-04-16 08:41 » (fallback)
Ne se déclenche que si le nom entrant est vide,
« Unnamed Workflow » ou « Workflow importé » (insensible à la
casse). Le paramètre `name` explicite de la requête reste
prioritaire. L'utilisateur peut renommer via le bouton éditer.
Pas de modification du schema workflow (champ `name` existant).
Tests manuels sur données réelles :
- notepad_enriched.json (tous nodes "Unknown") → fallback id OK
- Bloc-notes, Explorateur et Recherche (2) → « Léa Rechercher »
- workflow construit avec title 'Sans titre – Bloc-notes'
→ « Léa Bloc-notes » OK
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avant : CaptureLibrary.tsx utilisait sessionStorage (purgé à la
fermeture d'onglet), et CapturePanel.tsx maintenait une liste
concurrente sous une clé différente (captureLibrary vs
captureLibrary_v2) → deux vues désynchronisées qui s'effacent
toutes les deux dès qu'on ferme le navigateur.
Après :
- Nouveau service captureLibraryStorage.ts (load/save/compress)
comme point unique d'accès.
- Stockage en localStorage (persiste entre onglets et sessions).
- Clé unifiée 'captureLibrary_v2'.
- Migration automatique de sessionStorage → localStorage et de
l'ancienne clé 'captureLibrary' → nouvelle, lors du premier load.
- Thumbnails compressés JPEG qualité 80% et redimensionnés à
320×240 max avant stockage pour rester sous le quota navigateur
(5–10 MB selon navigateur).
- Gestion QuotaExceededError dans saveLibrary : élague les items
les plus anciens jusqu'à ce que ça passe (5 tentatives).
- Les deux composants consomment le même helper : fin de la
divergence de format (sessionId/favorite).
Diagnostic (bug reproduit par lecture du code, pas besoin de
navigateur) :
- CaptureLibrary.tsx:28,42,62 → sessionStorage/captureLibrary_v2
- CapturePanel.tsx:53,61 → sessionStorage/captureLibrary
→ Deux sources, toutes deux éphémères.
Vérif : `npx tsc --noEmit` passe (EXITCODE=0).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fichier SQLite vide (toutes tables à 0 lignes), tracé en git mais
jamais peuplé. La vraie source de vérité est `workflows.db`
(DATABASE_URL dans backend/.env → 3 workflows, 115 exécutions,
920 steps).
Risque éliminé : si `.env` n'était pas chargé (ex : systemd mal
configuré), SQLAlchemy retombait sur le fallback
`sqlite:///vwb_v3.db` et l'app créait/utilisait une BDD
complètement vide à côté de la vraie. Foot-gun classique.
Correctif :
- Fallback de app.py aligné sur workflows.db.
- Fichier vwb_v3.db supprimé du repo.
workflows.db reste seule source de vérité.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- B4 : supprime le double logging dans backend/app.py.
app.py est importé 2 fois (une fois comme __main__ via `python app.py`,
une fois comme module `app` via `from app import socketio` dans
api/websocket_handlers.py). Le RotatingFileHandler était donc ajouté
2× au root logger → chaque ligne loguée dupliquée. Fix : garde
idempotente qui vérifie si un handler vers vwb.log existe déjà.
- B6 : supprime les fichiers .pid résiduels (.backend.pid,
.frontend.pid, .frontend_v4.pid) et les ajoute au .gitignore
(avec *.lock, *.orig, *.bak).
- B7 : ajoute launch.sh (wrapper → run_v4.sh par défaut, legacy
→ run.sh), clarifie en tête de run.sh et run_v4.sh la distinction
frontend/ (legacy v3) vs frontend_v4/ (actif), et rectifie le
README.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GraphBuilder construit maintenant des ScreenState enrichis
(ui_elements + detected_text) au lieu de stubs vides, et associe
les clics aux UIElement par proximité spatiale.
Détails :
- __init__ accepte ui_detector, screen_analyzer, enable_ui_enrichment,
element_proximity_max_px (+ lazy resolver via singleton C1)
- _create_screen_states délègue à ScreenAnalyzer.analyze() — remplace
l'appel à _extract_text() qui n'existait plus depuis le Lot C
(bug silencieux : OCR cassé en prod depuis ce jour, caught except)
- _find_clicked_element : bbox contenant strict + fallback proximité
≤50px, préfère le plus petit bbox (form vs button)
- _build_click_target_spec : TargetSpec(by_role, by_text,
selection_policy="by_similarity") avec ancres dans context_hints
(anchor_element_id, anchor_bbox, anchor_center)
- _build_edges propage le ScreenState source aux builders d'action
- WorkflowPipeline passe ui_detector + enable_ui_enrichment au builder
Impact : matching prod 3-5x plus précis, TargetSpec ne sont plus
des "unknown_element" génériques, UIConstraint.required_roles se
remplit correctement via _extract_common_ui_elements (qui marchait
depuis toujours mais sur des state.ui_elements vides).
Tests e2e migrés vers enable_ui_enrichment=False (2.9s vs 67s) —
ils valident le pipeline DBSCAN/edges, pas la détection UI réelle.
15 nouveaux tests, 178 tests passants au total (incluant Lots A-E).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deux fixes pour que la CI collecte tous les tests unitaires :
1. RPA_API_TOKEN défini dans les env du workflow
- Sans : agent_v0/server_v1/api_stream.py fait sys.exit(1)
au module load (fail-closed P0-C), ce qui casse la collection
de tests/unit/test_env_setup.py (qui importe api_stream)
- Avec : token bidon qui permet aux imports de passer,
les tests mockent les vraies requêtes
2. Flask-SocketIO + deps socketio ajoutés à requirements-ci.txt
- web_dashboard/app.py importe `from flask_socketio import SocketIO`
- test_dashboard_routes.py importait app -> ModuleNotFoundError en CI
- Ces packages étaient explicitement exclus avant, mais sont
nécessaires pour les 37 tests du dashboard
Résultat local : 1723 passed, 39 failed (dette pré-existante
documentée dans l'audit — contamination entre tests, à traiter
séparément).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vrais bugs corrigés :
- core/execution/target_resolver.py : suppression de 5 lignes de dead code
après un return (vestige de refacto incomplète référençant des params
jamais assignés à self : similarity_threshold, use_spatial_fallback)
- agent_v0/agent_v1/core/executor.py:2180 : variable `prefill` référencée
mais jamais définie. Initialisation explicite ajoutée en amont
(conditionnée sur _is_thinking_popup, cohérent avec l'append du message)
Fichier supprimé :
- core/security/input_validator_new.py : contenu corrompu (texte inversé,
artefact de copier-coller), jamais importé nulle part, 550 erreurs ruff
à lui seul
Workflow CI :
- Exclusions ajoutées pour dossiers legacy connus cassés :
- agent_v0/deploy/windows_client/ (clone obsolète)
- tests/property/ (cf. MEMORY.md — imports cassés)
- tests/integration/test_visual_rpa_checkpoint.py (VisualMetadata
inexistant, déjà documenté)
Résultat : "ruff All checks passed!" sur core/ agent_v0/ tests/
(avec E9,F63,F7,F82 — syntax + undefined critiques).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Commit trivial pour valider le déclenchement de la CI Gitea Actions
après enregistrement du runner.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nouvelle méthode match_current_state_from_state(screen_state, workflow_id)
qui utilise directement le ScreenState enrichi (window_title, detected_text,
ui_elements) fourni par ExecutionLoop au lieu de reconstruire un stub
ScreenState("Unknown", ui_elements=[], ...).
Préfère HierarchicalMatcher si workflow chargeable, fallback FAISS sinon.
L'ancienne API match_current_state(screenshot_path, workflow_id) est
convertie en wrapper : appelle ScreenAnalyzer.analyze() puis délègue.
Rétrocompat préservée.
ExecutionLoop._execute_step utilise la nouvelle méthode -> plus de double
analyze() dans le chemin d'exécution (économie latence).
Premier vrai matching context-aware. 11 nouveaux tests + 2 tests
integration loop. 172 tests non-régression verts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avant : clé = phash seul
-> deux contextes différents avec même screenshot partageaient
la même entrée cache -> collisions silencieuses.
Après : clé composite {phash}|{md5(ctx)[:16]} avec ctx =
- window_title
- app_name
- enable_ocr
- enable_ui_detection
- workflow_id (isolation inter-workflows)
get_or_compute() kwargs-only. TTL 2s et éviction LRU inchangés.
invalidate_if_changed() continue de comparer uniquement les phash.
ExecutionLoop propage tout le contexte au cache.
8 nouveaux tests prouvant :
- même image + window différent = miss
- même image + app différent = miss
- même image + flags différents = miss
- même image + workflow_id différent = miss
- même image + même contexte = hit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Retrait de l'état global toxique :
- analyze() : kwargs-only enable_ocr, enable_ui_detection, session_id
- Ne mute JAMAIS self pour les flags (variables locales + branches)
- _resolve_ocr_instance() / _resolve_ui_detector_instance() : lecture seule
- _init_lock par instance pour lazy init concurrent safe
- session_id par appel, plus via mutation singleton
Avant : ExecutionLoop mutait analyzer._ocr, _ui_detector,
_ocr_initialized, _ui_detector_initialized pour désactiver OCR/UI.
Deux loops partageant le singleton se polluaient mutuellement.
Après : deux loops partageant l'analyzer sont complètement isolés.
Preuve par TestAnalyzerIsolationBetweenLoops (3 tests).
Singleton get_screen_analyzer() préservé — garde uniquement les
ressources lourdes, plus de contexte d'exécution.
9 nouveaux tests (3 isolation + 6 kwargs-only/lazy-init).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avant : source_similarity=1.0 hardcodé dans _check_preconditions
-> la contrainte EdgeConstraints.min_source_similarity était
silencieusement désactivée. Un edge passait toujours.
Après : propagation ExecutionLoop -> workflow_pipeline -> EdgeScorer
- select_best/rank/score_edge/_check_preconditions acceptent
source_similarity: float (kwargs-only)
- get_next_action() le propage
- execution_loop passe la confidence issue de match_current_state
La contrainte min_source_similarity est opérationnelle pour la
première fois. Preuve concrète par test_min_source_similarity_fail
et test_low_similarity_blocks_edge (edge rejeté si sim < seuil).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P0-B — /api/v1/traces/stream/image retiré de _PUBLIC_PATHS :
- Bearer token obligatoire pour upload d'image
- Évite uploads anonymes de contenu arbitraire
P0-C — Fail-closed si RPA_API_TOKEN absent :
- sys.exit(1) au démarrage avec message fatal
- Mode dev : RPA_AUTH_DISABLED=true pour désactiver explicitement
- Log INFO des 8 premiers chars du token (diagnostic)
Fix target_memory prefix empilé :
- Strip "memory_" répétés avant stockage dans replay_memory.py
- Évite "memory_memory_memory_template_matching" en base
live_session_manager : améliorations mineures de la gestion sessions.
10 tests auth API stream.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Onglet "🧹 Nettoyage" dans le dashboard (iframe vers port 5006)
- Indicateur d'état + bouton de démarrage si cleaner down
- Service systemd rpa-session-cleaner intégré au target rpa-vision
- svc.sh et services.conf incluent session-cleaner (port 5006)
P0-A — Auth dashboard Flask :
- HTTP Basic obligatoire sur tous les endpoints (sauf /health, /healthz)
- Credentials via DASHBOARD_USER + DASHBOARD_PASSWORD
- 13 tests
Nettoyage UI :
- Section "Détection Visuelle" OWL retirée (modèle remplacé par pipeline VLM)
- Dashboard préfère auto shot_*_blurred.png (avec ?raw=1 pour brut)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Lea.bat ne tue plus TOUS les pythonw.exe du poste (Jupyter, Spyder)
Kill ciblé uniquement sur le PID lu dans lea_agent.lock
- LeaServerClient utilise RPA_SERVER_URL (HTTPS prod) au lieu de
hardcode http://:5005
- Normalisation du slash final de l'URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>