Commit Graph

263 Commits

Author SHA1 Message Date
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
Dom
8589e87a13 fix: grounding uniquement dans les fenêtres, template pour la taskbar
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>
2026-04-05 21:19:36 +02:00
Dom
8a1dfc6e8b feat: acteur gemma4 — décide PASSER/EXECUTER/STOPPER quand target_not_found
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>
2026-04-05 21:05:37 +02:00
Dom
3bcf59e16f fix: message notification humain (plus de "yolo") + description cible améliorée
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>
2026-04-05 20:52:34 +02:00
Dom
46206d9396 feat: vérification CLIP avant chaque clic (filet de sécurité app)
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>
2026-04-05 18:49:19 +02:00
Dom
d3e928bebe feat: branchement workflow — actions magnétoscope enrichies avec CLIP
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>
2026-04-05 16:30:27 +02:00
Dom
a679fbb62b docs: Plan Acteur Intelligent V1 — architecture 3 niveaux
MACRO : planificateur LLM (décompose "traite les dossiers de janvier")
MÉSO : acteur décisionnel (regarde, comprend, décide, agit)
MICRO : grounding + exécution (localise et clique)

Phase 1 = workflows comme templates avec variables
Phase 2 = acteur qui compare états et décide
Phase 3 = planificateur macro avec boucles

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:41:01 +02:00
Dom
f0b311306d fix: grounding pour TOUT texte visible (OCR + VLM), auto-unload gemma4
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>
2026-04-05 12:24:44 +02:00
Dom
1c5ff42006 fix: ajouter position relative au prompt grounding (désambiguïsation)
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>
2026-04-05 12:04:46 +02:00
Dom
b09a3df054 fix: _app_name déplacé hors du bloc if (scope error) 2026-04-05 11:29:51 +02:00
Dom
fceb76de1f feat: gemma4 enrichit les éléments sans OCR via Docker (port 11435)
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>
2026-04-05 11:21:02 +02:00
Dom
6d4ff4f215 fix: vérification par nom d'APPLICATION, pas par titre exact
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>
2026-04-05 09:16:48 +02:00
Dom
2486e43def fix: cropper la fenêtre depuis le screenshot live (pas chercher _window.png)
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>
2026-04-05 09:09:13 +02:00
Dom
20b74286f7 feat: polling titre fenêtre au lieu de wait fixe (post-vérification)
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>
2026-04-05 08:49:48 +02:00
Dom
a1c97504ab feat: Phase 1 acteur — pré/post vérification titre fenêtre
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>
2026-04-05 00:09:08 +02:00
Dom
d6c7346898 fix: ne pas couper le replay au début (taskbar = unknown_window)
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>
2026-04-04 23:33:52 +02:00
Dom
90ee8ca8f4 fix: template matching sur fenêtre active + seuil 0.90
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>
2026-04-04 23:23:21 +02:00
Dom
84a91630e9 feat: grounding sur image fenêtre au lieu du full screen
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>
2026-04-04 23:12:30 +02:00
Dom
91614fbff0 fix: prompt natif bbox_2d pour le grounding Qwen2.5-VL
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>
2026-04-04 22:43:46 +02:00
Dom
c1ce6a3964 fix: séparer grounding (qwen2.5vl) et compréhension (gemma4)
- Grounding : qwen2.5vl:7b hardcodé (seul modèle avec bbox_2d précis)
- Compréhension/VLM : gemma4:e4b via RPA_VLM_MODEL (description, identification)
- Ajout think=False + num_predict=200 pour éviter le mode thinking gemma4
- Variable RPA_GROUNDING_MODEL pour override si besoin

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:48:00 +02:00
Dom
0bd0fbb8c5 fix: SomEngine sur CPU pour cohabiter avec Qwen2.5-VL GPU
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>
2026-04-01 09:30:00 +02:00
Dom
394342be7e feat: support vLLM (GPU) comme moteur de grounding, Ollama en fallback
_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>
2026-03-31 23:37:12 +02:00
Dom
6724f43950 fix: stratégie hybride OCR→grounding VLM / icônes→template matching
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>
2026-03-31 23:21:06 +02:00
Dom
d99b17394a feat: VLM grounding direct (Qwen2.5-VL) — nouvelle stratégie de résolution
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>
2026-03-31 18:55:00 +02:00
Dom
875367dea9 fix: template matching prioritaire pour icônes sans texte (by_text vide)
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>
2026-03-31 18:11:24 +02:00
Dom
a74056ca22 feat: anti-détection robot — Bézier mouse + frappe char-by-char
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>
2026-03-31 16:25:43 +02:00
Dom
6937b94f2a fix: 3 corrections — crop 80px, email AZERTY (@), icônes anchor match
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>
2026-03-31 16:21:02 +02:00
Dom
4f5c518d3a fix: anchor match sur screenshot entier + proximité élément SomEngine
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>
2026-03-31 15:51:18 +02:00
Dom
7dec3ab63a fix: rejeter bavardage VLM dans _vlm_identify_element
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>
2026-03-31 15:44:56 +02:00
Dom
68d5bb7dd1 fix: som_anchor_match déclenché quand by_text vide (icônes sans texte)
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>
2026-03-31 15:38:38 +02:00