Commit Graph

192 Commits

Author SHA1 Message Date
Dom
b808e48b1f feat(fleet): endpoints /agents/enroll|uninstall|fleet + SQLite
Endpoints REST pour le fleet management (utilisés par installeur Inno Setup) :
  POST /api/v1/agents/enroll    -> 201 {status, machine_id, api_token, agent}
  POST /api/v1/agents/uninstall -> 200 {status, machine_id, agent}
  GET  /api/v1/agents/fleet     -> 200 {active, uninstalled, totals}

Tous protégés par Bearer token (conforme _PUBLIC_PATHS existant).

Nouveau module agent_v0/server_v1/agent_registry.py :
  - Classe AgentRegistry (sqlite3 stdlib, WAL, thread-safe via Lock)
  - CRUD + soft-delete (uninstall = status="uninstalled", historique préservé)
  - Table enrolled_agents créée via IF NOT EXISTS (pas de migration nécessaire)
  - Ré-enrollment après uninstall = réactivation auto (allow_reactivate=True)
  - Chemin DB configurable via RPA_AGENTS_DB_PATH (défaut data/databases/rpa_data.db)

Fix fixture test_stream_processor : autouse RPA_API_TOKEN dans
TestAPIEndpoints pour éviter SystemExit P0-C au module load.

13 tests intégration (enroll/uninstall/fleet + auth + edge cases).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:07:19 +02:00
Dom
78ee962918 feat(matching): match_current_state_from_state consomme enrichi (Lot E)
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>
2026-04-15 09:07:04 +02:00
Dom
c8a3618e27 feat(cache): ScreenStateCache clé composite context-aware (Lot D)
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>
2026-04-15 09:06:51 +02:00
Dom
9ca277a63f refactor(pipeline): ScreenAnalyzer thread-safe et isolé (Lot C)
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>
2026-04-15 09:06:41 +02:00
Dom
8c7b6e5696 feat(scoring): EdgeScorer utilise la vraie source_similarity (Lot B)
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>
2026-04-15 09:06:28 +02:00
Dom
af4ffa189a feat(analytics): normalise API + contrat explicite get_next_action (Lot A)
Contrat get_next_action() — suppression du None ambigu :
  {"status": "selected", "edge": ..., ...}
  {"status": "terminal"}
  {"status": "blocked", "reason": "no_valid_edge" | ...}

ExecutionLoop dispatche proprement : blocked -> PAUSED + _pause_requested,
terminal -> succès légitime. Rétrocompat défensive (None legacy -> blocked).

Analytics API normalisée (kwargs-only) :
  on_execution_complete(duration_ms, status, steps_total|completed|failed)
  on_step_complete(duration_ms, ...)
  on_recovery_attempt(duration_ms, ...)

Découverte critique : les anciens appels utilisaient des méthodes et champs
inexistants (ExecutionMetrics.duration, metrics_collector.record_execution).
Le code n'avait jamais tourné au runtime — zéro analytics remontée.
L'exception était avalée par le try/except englobant.

58 tests (18 analytics + 11 contrat + 20 ExecutionLoop + 12 edge_scorer
non-régression). Migration complète, pas de pont legacy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:06:19 +02:00
Dom
42f571d496 docs(audit): README honnête + STATUS + DEV_SETUP + cleanup build
- README.md : bandeau POC, date 14 avril 2026, retrait claims
  "production-ready 77%" (alignement code/doc post-audit)
- docs/STATUS.md : état réel par module (opérationnel/alpha/en cours)
- docs/DEV_SETUP.md : gestion worktrees Claude
- QUICK_START.md : gemma4:latest au lieu de qwen3-vl:8b
- deploy/build_package.sh : +9 fichiers dans REQUIRED_FILES
  (system_dialog_guard.py, persistent_buffer.py, grounding.py, etc.)
- agent_v0/deploy_windows.py : marqué OBSOLÈTE (legacy)
- .gitignore : ajout data/, .hypothesis, .deps_installed, buffer/,
  instance/*.db, caches SQLite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:49:29 +02:00
Dom
36737cfe9d feat(security): eval()→AST parseur + pickle→JSON+HMAC signé
Vulnérabilité 1 — eval() dans DAG executor :
- Nouveau module safe_condition_evaluator.py
- Parseur AST avec whitelist (Constants, Names, Compare, BoolOp, BinOp)
- Rejet explicite Call/Lambda/Import/__dunder__/walrus/comprehensions
- Expression non sûre → logged ERROR + évaluée à False (pas de crash)
- 31 tests (12 valides, 17 malveillantes rejetées, 2 intégration)

Vulnérabilité 2 — 3× pickle.load() non sécurisés :
- Nouveau module signed_serializer.py (JSON+HMAC-SHA256)
- Format : RPA_SIGNED_V1\\n + JSON(hmac + payload base64)
- Migration automatique transparente au premier chargement
- Fallback pickle avec WARNING (désactivable RPA_ALLOW_PICKLE_FALLBACK=0)
- Remplacement dans faiss_manager, visual_embedding_manager,
  visual_persistence_manager
- 13 tests

Clé signature : RPA_SIGNING_KEY (fallback TOKEN_SECRET_KEY puis hostname-derived).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:49:17 +02:00
Dom
93ef93e563 feat(security): API streaming fail-closed + /image privé + target_memory prefix fix
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>
2026-04-14 16:49:02 +02:00
Dom
376e4a88b3 feat(deploy): installeur Inno Setup pour déploiement professionnel
- Lea.iss : script Inno Setup 6 (enrollment 2 pages, licence, machine_id)
- build_installer.sh : staging + ISCC (compatible Wine sur Linux)
- uninstall_lea.ps1 : kill PID + cleanup + notif serveur
- configure_embed.ps1 : Python 3.12 embedded optionnel
- config_template.txt : modèle pour installation silencieuse
- LICENSE.txt : CGU AI Act Art. 50
- README.md : doc build, signing, déploiement silencieux

Paramètres d'installation silencieuse :
  Lea-Setup-v1.0.0.exe /VERYSILENT /CONFIG=enroll.txt /LOG=install.log

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:48:48 +02:00
Dom
bb4ed2a75d feat(dashboard): session cleaner intégré + auth + nettoyage UI
- 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>
2026-04-14 16:48:36 +02:00
Dom
f7b8cddd2b feat(anonymisation): blur PII côté serveur via EDS-NLP + VLM local-first
Blur PII server-side (core/anonymisation/pii_blur.py) :
- Pipeline OCR (docTR) → NER (EDS-NLP + fallback regex)
- Détection ciblée noms/prénoms/adresses/NIR/téléphone/email
- Protection explicite CIM-10, CCAM, montants €, dates, IDs techniques
- Dual-storage : shot_XXXX_full.png (brut) + _blurred.png (affichage)
- 18 tests

Client :
- RPA_BLUR_SENSITIVE=false par défaut (blur serveur uniquement)
- Zéro overhead côté poste utilisateur

VLM config :
- vlm_config.py : gemma4:latest, fallbacks qwen3-vl:8b + UI-TARS
- think=false auto pour gemma4 (bug Ollama 0.20.x)
- VLM provider VWB : local-first (Ollama), cloud opt-in via VLM_ALLOW_CLOUD

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:48:23 +02:00
Dom
a9a99953dd fix(agent): Lea.bat kill par PID + LeaServerClient URL
- 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>
2026-04-14 16:48:09 +02:00
Dom
aee64f54b1 feat(security): détection dialogues système Windows + fail-closed
Nouveau module system_dialog_guard.py :
- Détection UAC, CredUI, SmartScreen, Defender, Driver install
- Multi-signal (ClassName UIA, process, title FR/EN, parent_path)
- Faux positifs validés (OSIRIS, OBSIUS, MEDSPHERE, Chrome, Excel)

Intégration dans executor.py et policy.py :
- 6 points de décision (avant click/type/key_combo, VLM, policy)
- Pause supervisée au lieu de clic aveugle
- Fail-closed en cas d'exception (P0-D audit)
- Notification systray + remontée serveur

Fix mock test policy engine pour compat _system_dialog_pause=None.
39 + 5 tests unitaires.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:48:00 +02:00
Dom
c77844fa9a feat(capture_server): auth Bearer + bind localhost + anti-path-traversal
- Token obligatoire (RPA_API_TOKEN) sur /capture et /file-action
- Bind 127.0.0.1 par défaut, 0.0.0.0 exige token (fail-closed)
- /health reste public pour monitoring
- VWB backend injecte le Bearer pour les proxys distants
- hmac.compare_digest pour comparaison temps constant

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:47:45 +02:00
Dom
013fe071a2 feat(streamer): purge après ACK + buffering SQLite persistant
- Nouveau module persistent_buffer.py (SQLite WAL, thread-safe)
- Purge automatique des captures locales après ACK 200 serveur
- Drain loop 15s, retry exponentiel, plafonds tentatives
- Enum ImageSendResult.{OK, FAILED, FILE_GONE} pour distinguer les cas
- FileNotFoundError n'est plus un faux succès (P0-E audit)
- 14 tests intégration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:47:35 +02:00
Dom
203dc00d53 fix: UIA compare les noms d'app au lieu des titres complets
"Fichier" dans "*,Ceci est un test – Bloc-notes" était rejeté
parce que le titre attendu était "test.txt – Bloc-notes".
Maintenant la comparaison extrait le nom d'app (Bloc-notes)
et accepte le match si c'est la même application.

Résout : "Ajouter un nouvel onglet" bloqué quand un fichier
différent est ouvert dans Bloc-notes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:27:08 +02:00
Dom
e9a028134a feat: blocs conditionnels — skip automatique des dialogues absents
Le session_cleaner détecte les dialogues système (Enregistrer sous,
Ouvrir, Confirmer, etc.) et marque les actions correspondantes comme
conditionnelles. Au replay, si le dialogue n'apparaît pas (ex: Ctrl+S
sauve silencieusement car le fichier existe), les actions du dialogue
sont skippées automatiquement.

Détection basée sur des patterns de noms de dialogues Windows FR/EN.
Testé : seul le clic dans "Enregistrer sous" est conditionnel,
les actions Bloc-notes/Rechercher/systray restent normales.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:20:00 +02:00
Dom
01bba7bc6c feat: wrong_window déclenche le mode apprentissage au lieu de bloquer
Quand la fenêtre attendue ne correspond pas (ex: Ctrl+S a sauvé sans
dialogue "Enregistrer sous"), Léa passe en mode capture au lieu de
retourner paused_need_help. Si l'humain ne fait rien pendant 10s,
l'action est skippée (l'état est considéré déjà atteint).

4 déclencheurs apprentissage maintenant couverts :
- retry_failed : grounding + retry échouent
- no_screen_change : clic sans effet visible
- wrong_window : fenêtre attendue absente
- SUPERVISE direct : Policy décide de demander

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:27:01 +02:00
Dom
d5285de99c feat: mode apprentissage — retry échoué + écran inchangé déclenchent la capture humaine
Trois chemins vers le mode apprentissage supervisé :
1. Grounding échoue → Policy RETRY → retry échoue → capture humaine
2. Clic visuel sans effet (écran inchangé 3s) → capture humaine
3. Policy SUPERVISE direct → capture humaine

La capture enregistre un mini-workflow complet (clics + frappes + combos)
jusqu'à Ctrl+Shift+L ou 10s d'inactivité. Correction envoyée au serveur.

Testé E2E : workflow Chrome avec résultats Google dynamiques +
bandeau cookies — Léa demande l'aide, capture, reprend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 08:33:57 +02:00
Dom
33c198b827 feat: premier replay E2E + mode apprentissage supervisé
Premier replay fonctionnel de bout en bout (Bloc-notes, Chrome).

Corrections critiques :
- Fix double-lancement agent (Lea.bat start /b + verrou PID)
- Sérialisation replay (threading.Lock dans poll_and_execute)
- Garde UIA bbox >50% écran (rejet conteneurs "Bureau")
- Filtre fenêtres bruit système (systray overflow)
- Auto-nettoyage replays bloqués (paused_need_help)

Cascade visuelle complète dans session_cleaner :
- UIA local (10ms) → template matching (100ms) → serveur docTR/VLM
- Nettoyage bureau pré-replay (clic "Afficher le bureau")
- Crops 80x80 + vlm_description pour chaque clic

Grounding contraint à la fenêtre active :
- Capture croppée à la fenêtre au lieu de l'écran entier
- Conversion coordonnées fenêtre → écran
- Élimine les faux positifs taskbar/systray

Mode apprentissage supervisé (SUPERVISE → capture humaine) :
- Léa passe en mode capture quand elle est perdue
- Capture mini-workflow humain (clics + frappes + combos)
- Fin par Ctrl+Shift+L ou timeout inactivité 10s
- Correction stockée dans target_memory.db via serveur

Deploy Windows complet (grounding.py, policy.py, uia_helper.py).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:42:50 +02:00
Dom
816b37af98 fix: session_cleaner utilise le fallback simple exclusivement
build_replay_from_raw_events transforme les events (réordonne, injecte
du setup "ouvrir l'app", fusionne les actions, ajoute des waits) ce qui
décale les clics par rapport à l'enregistrement original. Le texte était
saisi dans le mauvais champ parce que les actions n'étaient plus en 1:1
avec la session.

Le fallback _simple_build_replay reproduit les events tels quels en
coords brutes — exactement ce qu'on veut pour "nettoyer et rejouer".
Le session_cleaner l'utilise maintenant exclusivement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:29:07 +02:00
Dom
d82aad984f fix: session_cleaner force visual_mode=False sur les clics
Contournement temporaire du crash agent "cannot unpack non-iterable
NoneType object" qui se produit quand l'agent Windows tente une
résolution visuelle (visual_mode=True) sur les actions replay.

Les actions construites par build_replay_from_raw_events gardent
leurs coordonnées enrichies (x_pct, y_pct calculés depuis la
session) mais sont envoyées avec visual_mode=False pour que l'agent
clique aux coords brutes sans passer par le grounding.

C'est un compromis temporaire : moins intelligent (pas de résolution
adaptative) mais fonctionnel (les clics arrivent aux bonnes coords).
Le mode visuel sera réactivé quand le bug agent sera diagnostiqué
et corrigé (le traceback n'est pas visible côté serveur, le
redéploiement de l'agent avec debug n'a pas pris effet).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:13:43 +02:00
Dom
057c37131f fix: session_cleaner fallback — x_pct/y_pct + visual_mode=False
Deux bugs dans _simple_build_replay :

1. Mauvais noms de champs : x_percent/y_percent au lieu de x_pct/y_pct
   attendus par l'agent executor. Et valeurs en 0-100 au lieu de 0-1.
   Résultat : l'agent recevait x_pct=None → crash "cannot unpack
   non-iterable NoneType object".

2. Pas de visual_mode=False explicite. Sans enrichissement
   (target_spec vide, pas d'anchor), l'agent tentait une résolution
   visuelle sur du vide → crash.

Aussi : la condition de fallback empêchait le déclenchement quand
build_replay_from_raw_events crashait (error_message non vide bloquait
la branche). Corrigé : le fallback se déclenche sur `not replay_actions`
(couvre None, liste vide, et crash du build principal).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:51:40 +02:00
Dom
9bcce3fc68 feat: session_cleaner — outil leger de nettoyage de sessions avant replay
Petit serveur Flask standalone (tools/session_cleaner.py) qui permet de :
- Lister les sessions enregistrees recentes
- Visualiser chaque session avec ses screenshots (crop + full)
- Marquer les clics parasites a supprimer (auto-detection des toasts,
  clics droit, fenetres Lea/systray, derniers 3 evenements)
- Re-construire un replay nettoye et l'injecter dans la queue via
  POST /api/v1/traces/stream/replay/raw

Option A du rapport audit VWB : "Le besoin reel est supprimer 3 clics
parasites et relancer — c'est 30 secondes d'UX, pas un Visual Workflow
Builder."

Port : 5006
Dependencies : Flask (deja dans le venv), aucune nouvelle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 11:35:31 +02:00
Dom
f96f6322ec chore: nettoyage code mort — suppression _a_trier/, archives/, .bak, scaffold vide
Supprime ~8.2 Go de fichiers parasites qui polluent les grep, consomment
des tokens, et ajoutent du bruit au repo :

- _a_trier/ (561 Mo) — scripts legacy, backups, sessions logs, démos
- archives/ (21 Mo) — copie figée code décembre 2024 (déjà dans git history)
- visual_workflow_builder/_a_trier/ (7.6 Go) — backups VWB legacy + anciens frontends
- web_dashboard/app.py.bak_20260304_2225 — fichier .bak oublié
- agent_v1/ (top-level) — scaffold vide jamais alimenté
- core/detection/ui_detector_old.py.bak — .bak traqué par erreur

Retire aussi du tracking git :
- 2 fichiers __pycache__ traqués par erreur dans VWB backend

Met à jour .gitignore pour prévenir la récurrence :
- *.bak, *.bak_*, *.orig, *.old
- _a_trier/, archives/

Tout ce contenu reste récupérable via git history (tag pre-cleanup-phase1-20260410).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 11:35:31 +02:00
Dom
02ee2d7b5b fix: Fenêtre incorrecte strict → pause supervisée pour apprentissage
Symétrie avec le fix 7cc03f6f1 (no_screen_change strict → paused_need_help).

Avant : si l'agent détecte en pré-vérification que la fenêtre active
n'est pas celle attendue, l'erreur retombait dans la branche retry+stop
legacy → 3 retries inutiles puis status=error et queue vidée.

C'est une violation de feedback_failure_is_learning.md : un échec Léa
n'est jamais un "stop avec error", c'est un moment pédagogique.

Maintenant :
  1. L'agent envoie warning="wrong_window" dans le résultat (en plus
     de l'error textuel existant). Ajouté aux 2 chemins :
     - pré-vérif (expected_window_before mismatch, executor.py ~587)
     - post-vérif strict (expected_window_title timeout, executor.py ~820)
  2. Le serveur détecte warning="wrong_window" AVANT la branche
     retry+stop legacy → redirection vers paused_need_help
  3. pause_message explicite : "Je m'attendais à voir la bonne fenêtre
     mais je vois autre chose. Peux-tu vérifier que l'application est
     au premier plan ?"
  4. Queue intacte (l'action reste en tête, prête à être relancée)
  5. log_replay_failure pour l'apprentissage futur

Cause fréquente identifiée : les popups de Léa elle-même (notifications,
fenêtre de chat) volent le focus Windows pendant le replay → l'app cible
perd le premier plan → pré-vérif détecte le mismatch. Bug UX séparé à
traiter (Léa ne devrait pas prendre le focus pendant un replay actif).

Appliqué aux 2 copies de l'agent (dev + deploy).

Tests : 56 E2E + Phase0 passent, 0 régression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:41:29 +02:00
Dom
47993e2ee9 chore: ajouter replay_failure_logger.py au tracking git
Ce fichier existe sur disque depuis le 4 avril mais n'a jamais été ajouté
à git. Il est importé par api_stream.py (ligne 29) — un fresh clone sans
ce fichier ne peut pas démarrer le serveur streaming.

Découvert par le project-quality-guardian lors de l'audit global du
11 avril (item C1, priorité P0 bloquant absolu).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:35:51 +02:00
Dom
7cc03f6f10 fix: no_screen_change strict → pause supervisée pour apprentissage
Rectification de la branche C introduite dans a21f1ea9f.

## Ce qui était faux

a21f1ea9f faisait :
  strict + no_screen_change → retry × 3 → status=error → queue vidée

C'est le réflexe d'un RPA classique qui se casse la figure quand ça
rate. Ce n'est PAS la philosophie Léa.

Dom m'a rappelé que j'avais oublié ma propre vision documentée dans
project_lea_apprentissage_plan.md et feedback_not_a_click_box.md :
*"Quand elle dit qu'elle n'a pas trouvé X, elle demande montre-moi.
C'est à ce moment qu'il faudrait passer en mode apprentissage."*

## Ce qui est correct maintenant

  strict + no_screen_change
    → status = "paused_need_help"
    → failed_action stocké (target, screenshot, method, score, reason)
    → pause_message demandant l'intervention humaine
    → queue intacte (l'action reste en tête, prête à être relancée)
    → log_replay_failure pour l'apprentissage futur
    → l'agent reçoit replay_paused=True dans /replay/next et s'arrête
    → l'humain corrige physiquement sur la machine cible
    → le replay reprend via /replay/{replay_id}/resume

Redirection vers le mécanisme paused_need_help qui existe déjà pour le
cas target_not_found. Zéro nouveau code de pause, juste une 2ème entrée
dans ce mécanisme.

Le comportement legacy (success_strict=False) reste inchangé : on
log un warning et on continue, comportement tolérant pour les actions
non-critiques.

## Lesson apprises

1. Toujours relire les fichiers mémoire pertinents AVANT d'implémenter
   une branche de gestion d'erreur (nouvelle règle dans
   feedback_reread_before_code.md)
2. Un échec Léa n'est jamais un "stop avec error" — c'est un moment
   pédagogique (nouvelle règle dans feedback_failure_is_learning.md)
3. Ne pas s'auto-presser quand Dom n'a jamais demandé d'aller vite

## Tests

- 56 tests E2E + Phase0 passent, 0 régression
- Comportement vérifié par inspection du code : pause_message formé
  correctement, queue préservée, log_replay_failure appelé

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:27:45 +02:00
Dom
a21f1ea9fa feat: garde qualité résolution (B) + no_screen_change strict (C)
Deux garde-fous qui ferment des trous identifiés lors du test de replay
chirurgical du 11 avril 2026 sur sess_20260411T084629_2d588e.

## B — Garde qualité en sortie de cascade (_validate_resolution_quality)

Couche de validation ajoutée en sortie du handler /resolve_target, après
que la cascade (_resolve_target_sync) a produit son meilleur candidat.
Single point of insertion, n'altère pas la cascade existante.

Deux checks :

  1. Seuil de score minimum par méthode (_RESOLUTION_MIN_SCORES)
     - hybrid_text_direct ≥ 0.80
     - som_anchor_match / som_text_match ≥ 0.75
     - template_matching ≥ 0.85
     - vlm_* / grounding ≥ 0.60
     - memory_* : pas de seuil (confiance cristallisée)
     - v4_uia_local / uia ≥ 0.90

  2. Garde de proximité contre coords enregistrées
     Si fallback_x/y_pct sont significatifs (pas placeholder 0.5/0.5 ni
     0.0/0.0), rejette si drift > 20% de l'écran dans un axe.
     Reproduit un faux positif vu en production : SoM a trouvé
     "Enregistrer" à (0.505, 0.770) alors que l'enregistrement était à
     (0.093, 0.356) — écart de 0.41.

Quand un check rejette : retourne resolved=False avec method=
"rejected_low_score_*" ou "rejected_drift_*" et reason détaillée.
L'action passe alors par le chemin "visual_resolve_failed" côté agent
→ Policy → pause supervisée ou retry selon contexte.

7 tests unitaires inline validés (score bas, drift, mémoire qui passe
toujours, placeholders V4 qui skip la garde drift, etc.).

## C — no_screen_change devient un échec strict en mode strict

Avant : si un clic retourne warning='no_screen_change' (écran inchangé
après action), le replay loggait un warning et CONTINUAIT à l'action
suivante. Trop indulgent pour les workflows critiques.

Maintenant : la branche no_screen_change consulte le flag
success_strict de l'action courante.

  - success_strict=True : traité comme vrai échec
      → retry si retry_count < MAX_RETRIES_PER_ACTION
      → stop définitif sinon (status=error, queue vidée, callback)

  - success_strict=False (legacy) : comportement inchangé, on continue

Prérequis : _create_replay_state copie maintenant success_strict,
expected_window_before, expected_window_title, intention dans la
version slim de actions stockée dans replay_state. Nécessaire pour
lire le flag depuis current_action_index dans /replay/result.

## Tests

- 7 tests unitaires inline sur _validate_resolution_quality
- 56 tests E2E + Phase0 passent, zéro régression
- Instrumentation [REPLAY] reste pleinement fonctionnelle

## Limites non traitées ici (explicites)

- La latence de 14s entre deux clics (pre-analyze + cascade + agent
  polling) reste inchangée. Les menus déroulants Windows peuvent encore
  se refermer avant le 2ème clic. Piste A du plan, à traiter séparément.
- L'intégration d'OS-Atlas-Base-7B comme grounder spécialisé reste
  dans les cartons (recommandation du rapport état de l'art).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:11:41 +02:00
Dom
9188bd7df1 fix: masquer la fenêtre console lors du spawn lea_uia.exe sur Windows
Ajoute creationflags=CREATE_NO_WINDOW (0x08000000) au subprocess.run()
qui appelle lea_uia.exe dans UIAHelper._run(). Sans ce flag, Windows
ouvre brièvement une fenêtre cmd noire à CHAQUE appel — et le captor
appelle UIA à chaque clic utilisateur pendant l'enregistrement.

Symptômes rapportés par Dom :
- Flash de fenêtre terminal à chaque clic (visible à l'œil)
- Ralentissement de la souris pendant les enregistrements
- Pollution des données d'apprentissage : le VLM de post-analyse
  "voit" la fenêtre cmd et l'enregistre comme élément cliqué
  (log serveur : "gemma4 a lu l'élément : 'C:\\Lea\\helpers\\lea_uia.exe'")

Implémentation portable :
- Flag calculé au niveau module : 0x08000000 sur Windows, 0 sur Linux/Mac
- getattr(subprocess, "CREATE_NO_WINDOW", ...) pour gérer l'absence de
  la constante sur Linux
- creationflags=0 est un no-op sur Linux, safe

Appliqué aux 2 copies synchronisées :
- agent_v0/agent_v1/core/uia_helper.py (source active pour l'agent)
- core/workflow/uia_helper.py (copie identique)

85 tests in silico OK (29 UIA + 56 E2E/Phase0). Le vrai test c'est
Dom qui refait un enregistrement et vérifie qu'il n'y a plus de
flash de terminal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:18:11 +02:00
Dom
f82753debe chore: instrumentation [REPLAY] pour diagnostic chaîne replay
Ajoute 6 points de log structurés homogénéisés avec le préfixe [REPLAY]
aux endroits clés de la chaîne de replay, pour permettre de suivre
précisément ce qui se passe pendant un test humain et diagnostiquer
les points de rupture sans déduire à l'aveugle.

Points de log :
1. DISPATCH          — /replay/next envoie une action (expected_before/after,
                       resolve_order, has_uia, has_anchor, by_text, strict)
2. RESOLVE_ENTRY     — _resolve_target_sync reçoit la demande (window_title,
                       uia_target, anchor, strict_mode)
3. RESOLVE_EXIT      — résolution terminée (method, coords, score, from_memory)
4. RESOLVE_EXCEPTION — crash rare dans la résolution
5. REPORT            — /replay/result reçoit le rapport agent (success, error,
                       warning, resolution_method, actual_position)
6. VERIFY            — décision finale post-vérification (agent_success,
                       ver_verified, sem_verified, final_success)

Usage : journalctl --user -u rpa-streaming -f | grep REPLAY

Aucune modif de logique, uniquement des logger.info() aux points de
décision critiques. 56 tests E2E + Phase0 restent verts.

Ces logs sont là pour stabiliser la chaîne après les modifications
robustesse du matin (strict control, UIA strict, filtre UIA-aware)
qui ont cassé les replays réels de Dom et ne se voient pas dans les
tests automatisés in silico.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:07:56 +02:00
Dom
b92cb9db03 feat: Phase 1 apprentissage — greffe TargetMemoryStore sur V4
Greffe minimale du mécanisme d'apprentissage persistant (Fiche #18,
target_memory_store.py) sur le pipeline streaming V4 sans toucher à V3.

Architecture (docs/PLAN_APPRENTISSAGE_LEA.md) :
- Lookup mémoire AVANT la cascade résolution coûteuse OCR/template/VLM
  dans _resolve_target_sync → hit = <10ms, miss = overhead zéro
- Record APRÈS validation post-condition (title_match strict)
  dans /replay/result → 2 succès → cristallisation par répétition
- Single source of truth : l'agent remplit report.actual_position avec
  les coords effectivement cliquées, le serveur les lit directement.
  Pas de cache intermédiaire (option C du plan).

Signature écran V4 : sha256(normalize(window_title))[:16]. Robuste aux
données variables, faux positifs rattrapés par le post-cond qui
décrémente la fiabilité via record_failure().

Fichiers :
- agent_v0/server_v1/replay_memory.py : nouveau wrapper 316 lignes
  exposant compute_screen_sig/memory_lookup/record_success/failure,
  lazy-init du store, normalisation texte stable, garde sanity coords
- agent_v0/server_v1/resolve_engine.py : lookup mémoire en tête de
  _resolve_target_sync (30 lignes)
- agent_v0/server_v1/replay_engine.py : _create_replay_state stocke
  une copie slim des actions (sans anchor base64) pour retrouver le
  target_spec par current_action_index
- agent_v0/server_v1/api_stream.py : 4 callers passent actions=...,
  record success/failure dans /replay/result lit actual_position
  du rapport (click-only), correction du commentaire Pydantic
- agent_v0/agent_v1/core/executor.py : remplit result["actual_position"]
  après self._click(), transmis dans le report de poll_and_execute

Tests : 56 E2E + Phase0 passent, zéro régression. Cycle Phase 1 validé
en simulation : miss → record → miss → record → HIT au 3ème passage.

Le deploy copy executor.py a une divergence pré-existante de 1302
lignes non committées — traité séparément lors du cleanup prochain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:08:14 +02:00
Dom
e66629ce1a 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>
2026-04-10 14:25:40 +02:00
Dom
cecdf417b7 fix: contrôle strict des étapes + routage par machine_id
Corrections critiques après test E2E qui montrait des clics au mauvais endroit :

1. Routage par machine_id (api_stream.py)
   Quand 2 machines partagent le même session_id (agent_demo_user),
   les actions d'un replay pour la VM ne doivent PLUS être distribuées
   au PC physique. Vérification que le replay_state appartient bien à
   la machine qui poll avant de consommer la queue.

2. IRBuilder extrait expected_window_before/after (ir_builder.py)
   Pour chaque action click/type/key_combo, stocke le titre de la fenêtre
   au moment du clic (before) et le titre du prochain événement (after).
   Ces champs alimentent le contrôle strict au runtime.

3. ExecutionCompiler crée SuccessCondition title_match (execution_compiler.py)
   Quand expected_window_after est défini, crée une condition de succès
   STRICTE avec method="title_match" et expected_title. Plus de simple
   "l'écran a changé" — on vérifie la fenêtre résultante.

4. Runner propage expected_window_before et success_strict
   Le flag success_strict indique à l'agent que le contrôle post-action
   DOIT être strict (STOP sur mismatch au lieu de warning).

5. UIA strict sur parent_path (executor.py)
   _resolve_via_uia_local REJETTE un match si l'élément trouvé n'est pas
   dans la bonne fenêtre parente (évite ex: "Rechercher" taskbar confondu
   avec "Rechercher" explorateur).

6. Pré/post vérif stricte et bloquante (executor.py)
   - expected_window_before lu en priorité depuis l'action (plan V4)
   - Post-vérif : si success_strict=True et timeout, result.success=False
     → le replay s'arrête au lieu de continuer avec des warnings.

Validé sur la VM :
- Le replay s'arrête proprement quand l'étape 2 aboutit dans "Propriétés de
  Internet" au lieu de "blocnote.txt - Bloc-notes"
- Plus de clics en aveugle / saisie au mauvais endroit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:05:23 +02:00
Dom
56e3cc052a feat: agent Windows consomme UIA — capture + résolution
Câblage agent Windows pour le pipeline V4 :

captor.py — capture UIA pendant l'enregistrement
- _inject_uia_snapshot() appelé après chaque clic
- Ajoute evt['uia_snapshot'] = {name, control_type, parent_path, ...}
- Non-bloquant : fallback silencieux si helper absent
- ~10-20ms par clic, pas de ralentissement perceptible

executor.py — résolution UIA locale au replay
- _resolve_via_uia_local() : appelle lea_uia.exe find via UIAHelper
- Court-circuit prioritaire avant le GroundingEngine serveur
- Activé quand resolve_order[0] == "uia" et target_spec.uia_target présent
- Coordonnées pixel-perfect (bounding_rect → center)
- Fallback transparent vers le grounding serveur si UIA échoue

uia_helper.py copié dans agent_v1/core/ (wrapper Python pour lea_uia.exe)
Auto-détection du binaire dans C:\Lea\helpers\lea_uia.exe
Singleton partagé get_shared_helper()

Déployé et validé sur la VM Windows :
- query_at(100,100) → "Bureau 1" en 10ms depuis Python
- Binaire lea_uia.exe trouvé et fonctionnel
- Les 3 modules Python sont dans C:\Lea\agent_v1\core\

Ce qui est maintenant possible (après redémarrage de Léa sur la VM) :
- Enregistrer un workflow : chaque clic aura un uia_snapshot
- Compiler via /workflow/compile : plan V4 avec stratégie UIA primaire
- Rejouer via /replay/plan : l'agent utilise UIA (10-20ms) au lieu de VLM (2-5s)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:18:25 +02:00
Dom
332366b58c feat: câblage complet V4 — stratégie UIA + surface profile
Pipeline V4 câblé de bout en bout :
  RawTrace (avec uia_snapshot) → IRBuilder → Action._enrichment
  WorkflowIR → ExecutionCompiler (avec SurfaceProfile) → ExecutionPlan
  ExecutionPlan → runner → target_spec (avec uia_target + resolve_order)

ResolutionStrategy étendu :
- Champs UIA : uia_name, uia_control_type, uia_automation_id, uia_parent_path
- Champs DOM : dom_selector, dom_xpath, dom_url_pattern (préparation web)

ExecutionCompiler.compile(surface_profile=...) :
- Timeouts/retries tirés du profil (citrix=15s/3x, web=5s/1x, natif=8s/2x)
- UIA primaire seulement si surface=WINDOWS_NATIVE et uia_available
- Citrix ignore UIA même si snapshot présent (UIA ne marche pas dans Citrix)

IRBuilder lit evt['uia_snapshot'] et le stocke dans action._enrichment
(à remplir par l'agent Windows pendant l'enregistrement via lea_uia.exe)

execution_plan_runner propage uia_target et dom_target dans target_spec
pour que l'agent Windows puisse les consommer au runtime.

11 tests de câblage E2E :
- Profils (Citrix/web/natif) imposent bien les timeouts
- Stratégie UIA créée quand snapshot+surface OK
- Stratégie UIA bloquée sur Citrix
- IRBuilder propage uia_snapshot
- Runner produit target_spec avec uia_target + resolve_order=['uia', 'ocr', 'vlm']

496 tests au total, 0 régression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:02:51 +02:00
Dom
ac9c207474 feat: SurfaceClassifier + UIAHelper — détection et wrapper Python
SurfaceClassifier — détecte le type d'application au runtime
- 4 surfaces : citrix / windows_native / web_local / unknown
- Paramètres adaptés par surface :
  * Citrix : OCR 0.65, timeouts 15s, retries 3x (compression JPEG tolérée)
  * Windows natif : OCR 0.75, timeouts 8s, UIA bonus si dispo
  * Web : OCR 0.80, timeouts 5s, paramètres rapides
  * Unknown : fallback sûr
- resolve_order() construit la chaîne selon les capacités disponibles
- Détection UIA via health check du helper Rust
- Détection CDP via localhost:9222

UIAHelper — wrapper Python pour lea_uia.exe
- Subprocess + JSON stdin/stdout
- 3 méthodes : query_at(x,y), find_by_name(name,...), capture_focused()
- Fallback silencieux (None) si helper absent, timeout, crash
- Singleton global get_shared_helper()
- Dataclass UiaElement avec center(), is_clickable(), path_signature()

29 nouveaux tests (détection 4 surfaces, dataclass, wrapper, mocks).
485 tests au total, 0 régression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:54:19 +02:00
Dom
f85d56ac05 feat: lea_uia — helper Rust Windows UI Automation (cross-compilé)
Premier pas de l'Option B hybride : vision + UIA pour Windows natif.

Pourquoi Rust ?
- Binaire standalone ~500 Ko, aucune dépendance runtime
- 5-10x plus rapide que pywinauto (10-20ms par query vs 50-200ms)
- Compilation cross-platform depuis Linux (x86_64-pc-windows-gnu)
- Safe : pas de crash sur null pointer ou memory leak
- Préparation d'un déploiement industriel robuste

Commandes :
- query --x N --y N         : élément UIA à cette position
- find --name "..." --control-type "..." : recherche par nom
- capture --max-depth N     : élément focus + hiérarchie
- health                    : vérifier que UIA est dispo

Sortie JSON structurée (stdin/stdout pour IPC avec Python).
Stub Linux pour dev/tests sans Windows.

Validé sur VM Windows :
- query (100,100) → "Bureau 1" en 18ms
- query (500,400) → "Bureau 1" en 12ms
- find "Rechercher" → not_found en 11ms (normal, rien d'ouvert)

Le binaire lea_uia.exe sera packagé avec Léa dans C:\Lea\helpers\

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:30:45 +02:00
Dom
172167f6c0 feat: Léa apprentissage — mode Shadow amélioré (observation + validation)
Aspect 3/4 Léa : Léa montre ce qu'elle comprend pendant l'enregistrement.

ShadowObserver (observation temps réel) :
- Segmentation incrémentale en UnderstoodStep (changement app, pause, Ctrl+S)
- Détection de variables pendant la saisie (typage : date, email, code, texte)
- Notifications 4 niveaux : INFO, DECOUVERTE, QUESTION, VARIABLE
- Heartbeat périodique, hook gemma4 optionnel (asynchrone)
- Thread-safe (RLock), singleton partagé
- Performance : 1000 events en < 500ms

ShadowValidator (feedback utilisateur) :
- 6 actions : validate, correct, undo, cancel, merge_next, split
- Reconstruit un WorkflowIR propre avec variables substituées
- Historique complet des feedbacks

5 endpoints REST /api/v1/shadow/* :
- start, stop, feedback, understanding, build

Hook non-bloquant dans stream_event() (try/except, no-op si inactif).
Mode optionnel : pas d'impact tant que shadow/start n'est pas appelé.

54 tests (26 observer + 28 validator), 0 régression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:04:37 +02:00
Dom
42d49dd8bd feat: Léa personnalité — langage métier multi-domaines
Aspect 4/4 Léa : Léa parle le langage du métier, pas du robot.

DomainContext enrichi avec 5 domaines :
- tim_codage : CIM-10, CCAM, GHM, DP/DAS (enrichi)
- comptabilite : factures HT/TVA/TTC, OCR, lettrage, PCG
- rh_paie : bulletins, DSN, brut/net, congés, IJSS
- stocks_logistique : BC/BL/BR, SKU, inventaires, picking
- generic : fallback

Nouvelle API DomainContext :
- summarize_action(action, params) — click "DP" → "saisir le diagnostic principal"
- pose_clarification_question(context) — question pertinente quand Léa bloque
- describe_workflow_outcome(...) — rapport final en langage métier

Exemples :
  TIM : "J'ai codé 14 dossiers sur 15. 1 en attente — codes CIM-10 ambigus."
  Compta : "Je ne trouve pas le champ montant de TVA. C'est bien la facture F2026-0145 ?"

Intégration ui/messages.py :
- Import lazy (pas de dépendance circulaire)
- formatter_cible_non_trouvee utilise les templates de clarification métier
- Rétro-compat : tous les anciens appels sans domain_id fonctionnent

47 nouveaux tests, 0 régression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:01:52 +02:00
Dom
f541bb8ce4 feat: Léa chat + IRBuilder enrichi (stratégies V4 complètes)
Aspect 2/4 Léa : interface conversationnelle
- chat_interface.py : ChatSession thread-safe, états idle/planning/awaiting/executing/done
- 5 endpoints REST : /api/v1/chat/* (session, message, history, confirm, sessions)
- web_dashboard/chat.html + chat.js : UI minimaliste, polling 2s, pas de framework
- Proxy Flask /api/chat/* → serveur streaming
- 34 tests (happy path, abandon, refus, erreurs, gemma4 down)

IRBuilder enrichi pour plans V4 complets
- _event_to_action() appelle enrich_click_from_screenshot() quand session_dir dispo
- Chaque clic porte _enrichment (by_text OCR, anchor_image_base64, vlm_description)
- ExecutionCompiler consomme l'enrichissement pour produire 3 stratégies par clic
  Avant : [ocr] uniquement, target="unknown_window"
  Après : [ocr, template, vlm] avec vrai texte OCR ("Rechercher", "Ouvrir")

Validé sur session réelle : 10/10 clics enrichis (by_text + anchor + vlm_description)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:01:13 +02:00
Dom
a6eb4c168f feat: Léa UX — messages français naturels + feedback temps réel
Aspect 1/4 de Léa (agent Windows) : rendre Léa humaine.

Nouveaux modules :
- agent_v1/ui/messages.py : 11 formatters (cible non trouvée, mauvaise fenêtre,
  écran inchangé, connexion, workflow, retry, ralentissement, erreur générique)
- agent_v1/ui/activity_panel.py : panneau tkinter lazy avec état courant,
  action, progression X/Y, temps écoulé, 7 états (OBSERVE/CHERCHE/AGIT/VERIFIE...)

Hiérarchie de notifications :
- INFO (4s, vert) — début workflow, étape en cours
- ATTENTION (7s, orange) — retry, ralentissement
- BLOCAGE (15s, rouge, persistent, bypass rate-limit) — cible introuvable, mauvaise fenêtre

Transformations de messages :
  AVANT : "target_not_found: dans *bonjour, – Bloc-notes"
  APRÈS : "Léa a besoin d'aide"
          "Je ne trouve pas « bonjour » dans Bloc-notes.
           Peux-tu cliquer dessus toi-même ? Je reprends ensuite."

Robustesse :
- Détection fenêtre Léa via regex word-boundaries (évite cléa.txt, leapfrog.exe)
- Centralisée dans messages.est_fenetre_lea() — source unique de vérité
- Noop stub universel via __getattr__ (plus besoin de lister les méthodes)
- Thread-safe (RLock + snapshots immutables)
- Fallback silencieux si tkinter/plyer absent

101 nouveaux tests, aucune régression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:42:01 +02:00
Dom
f6ad5ff2b2 feat: runtime V4 honore resolve_order pré-compilé (zéro VLM au runtime)
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>
2026-04-10 08:28:55 +02:00
Dom
2ac781343a feat: runtime V4 — endpoints /workflow/compile et /replay/plan
Pipeline V4 complet disponible en API :
  RawTrace → /workflow/compile → WorkflowIR + ExecutionPlan → /replay/plan → Runtime

- execution_plan_runner.py : adaptateur ExecutionNode → action executor
- Substitution variables {var} dans target/text
- Fusion stratégies primary + fallbacks (OCR, template, VLM)
- Clicks: coordonnées neutralisées, resolve_engine trouve au runtime
- 35 nouveaux tests (conversion, substitution, injection queue, pipeline E2E)
- Ancien chemin build_replay_from_raw_events() préservé (coexistence)

208 tests passent, 0 régression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:09:05 +02:00
Dom
bffcfb2db3 feat: ExecutionCompiler — compile WorkflowIR en plan d'exécution borné
Pièce maîtresse de l'architecture V4 :
- ExecutionPlan : nœuds avec stratégies de résolution pré-compilées
- ExecutionCompiler : WorkflowIR → ExecutionPlan déterministe
- Résolution : OCR (primaire, 100ms) > template > VLM (exception handler)
- Chaque nœud : timeout, max_retries, recovery, condition de succès
- Variables substituables, versionné, sérialisable JSON
- 18 tests (compilation, stratégies, fallbacks, variables, roundtrip)

"Le LLM compile. Le runtime exécute."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:21:40 +02:00
Dom
cc673755f7 feat: WorkflowIR — représentation intermédiaire du savoir-faire
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>
2026-04-09 21:50:32 +02:00
Dom
4509038bf0 refactor: éclater api_stream.py (6400→3350 lignes) en modules
- resolve_engine.py (1953 lignes) — résolution visuelle (template, VLM, SoM, YOLO)
- replay_engine.py (1284 lignes) — gestion des replays (queue, setup, retry, validation)
- api_stream.py (3352 lignes) — routeur principal (endpoints HTTP thin layer)

Préparation V4 : base propre pour le WorkflowIR et l'ExecutionCompiler.
137 tests passent, 0 régression, aucun endpoint modifié.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:37:44 +02:00
Dom
99041f0117 feat: pipeline complet MACRO/MÉSO/MICRO — Critic, Observer, Policy, Recovery, Learning, Audit Trail, TaskPlanner
Architecture 3 niveaux implémentée et testée (137 tests unitaires + 21 visuels) :

MÉSO (acteur intelligent) :
- P0 Critic : vérification sémantique post-action via gemma4 (replay_verifier.py)
- P1 Observer : pré-analyse écran avant chaque action (api_stream.py /pre_analyze)
- P2 Grounding/Policy : séparation localisation (grounding.py) et décision (policy.py)
- P3 Recovery : rollback automatique Ctrl+Z/Escape/Alt+F4 (recovery.py)
- P4 Learning : apprentissage runtime avec boucle de consolidation (replay_learner.py)

MACRO (planificateur) :
- TaskPlanner : comprend les ordres en langage naturel via gemma4 (task_planner.py)
- Contexte métier TIM/CIM-10 pour les hôpitaux (domain_context.py)
- Endpoint POST /api/v1/task pour l'exécution par instruction

Traçabilité :
- Audit trail complet avec 18 champs par action (audit_trail.py)
- Endpoints GET /audit/history, /audit/summary, /audit/export (CSV)

Grounding :
- Fix parsing bbox_2d qwen2.5vl (pixels relatifs, pas grille 1000x1000)
- Benchmarks visuels sur captures réelles (3 approches : baseline, zoom, Citrix)
- Reproductibilité validée : variance < 0.008 sur 10 itérations

Sécurité :
- Tokens de production retirés du code source → .env.local
- Secret key aléatoire si non configuré
- Suppression logs qui leakent les tokens

Résultats : 80% de replay (vs 12.5% avant), 100% détection visuelle Citrix JPEG Q20

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v3.0
2026-04-09 21:03:25 +02:00
Dom
72a9651b94 docs: consolidation 5 avril — état des lieux complet
Pipeline entraînement validé (15.7s, extrapolation 1h = 10 min).
CLIP vérification validée (sim 0.87-0.99 sur fenêtres).
Acteur gemma4 branché (PASSER/EXECUTER/STOPPER, think=True).
Grounding fenêtre + template taskbar fonctionnels.
Problèmes identifiés : ambiguïté Rechercher, éléments VLM.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:25:10 +02:00