Commit Graph

362 Commits

Author SHA1 Message Date
Dom
5ea4960e65 backup: snapshot post-démo GHT 2026-05-19
Some checks failed
tests / Lint (ruff + black) (push) Successful in 1m50s
tests / Tests unitaires (sans GPU) (push) Failing after 1m50s
tests / Tests sécurité (critique) (push) Has been skipped
Backup état complet après enregistrement vidéo démo de bout en bout.
À utiliser comme point de référence pour la consolidation post-démo.

Changements majeurs de la session 18-19 mai :
- AIVA-URGENCE : page autonome avec preset URL + auto-focus chain
- Workflow Demo_urgence_3_db : merge linux_db + steps AIVA + pause humaine NoMachine
- Bypass LLM (static_result / static_text) dans replay_engine
  pour démos déterministes sans appel Ollama
- Fix api_stream:3013 — replay_paused au premier polling /next
- dag_execute : lift duration_ms vers top-level pour wait runtime
- NPM bypass auth /aiva-urgence/ via location ^~ (proxy_host/10.conf hors git)
- scripts/cancel-replays.sh — workaround Stop VWB qui ne purge pas la queue

Anchors visuels (468) forcés dans le commit pour garantir restorabilité.
DB workflows actuelle + ~12 .bak DB de la journée incluses.

Sujets identifiés pour consolidation post-démo (TODO) :
1. Bug VWB recapture anchor ne régénère pas le PNG
2. Léa client accumule état mémoire (restart périodique requis)
3. Stop VWB ne purge pas la queue serveur (lien manquant vers /replay/cancel)
4. Bug coord client mss tronqué 2560x60 → mapping Y cassé
5. delay_before/delay_after ignorés au runtime (fix partiel duration_ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:55:06 +02:00
Dom
f2212e77e3 feat(t2a): bench_t2a_dryrun.py + t2a_mappings.py - mini-bench standalone 11 dossiers POC
Bench standalone qui exécute build_dpi_enriched + appel LLM sur les 11
dossiers POC GHT Sud 95 (docs/clients/ght_sud_95/mockup_easily_assure/data.js),
sans passer par Demo_urgence_2 ni Léa/Windows. Permet de mesurer la
convergence durée/décision Python ↔ LLM sur un panel représentatif AVANT
d'écrire le garde-fou serveur du commit 2.

core/llm/t2a_mappings.py :
- Module partagé TERRAIN_VERS_T2A (4 entrées validées par Dom 12/05)
- Importé par le bench, sera importé aussi par le garde-fou serveur commit 2
- Cas non mappés volontairement documentés (Retour structure d'origine,
  chaîne vide pour statut_attente)

scripts/bench_t2a_dryrun.py :
- Parsing data.js via node (vm.runInContext) → 11 dossiers en JSON
- Reconstruction d'un dpi_raw plat simulant l'OCR scroll auto :
  bandeau Easily Assure répété 5x (1 par onglet) + sections motif /
  examens / imagerie / notes médicales / Synthèse Urgences au format
  LIBELLÉ    VALEUR
- NE bypasse PAS build_dpi_enriched : le dpi_raw est texte plat re-parsé
  par la fonction (test de robustesse réel du parser regex)
- Appel LLM déterministe : temperature=0, seed=42, model=gemma4:31b-cloud
- Vérification empirique du respect du seed (2 appels successifs sur 1er
  dossier, comparaison decision/durée/justif) → warning si bruit cloud
- 4 traces structurées par dossier dans logs/t2a_dryrun/<IPP>_<ts>.log :
  [t2a_dryrun_metadata] / [t2a_dryrun_prompt] / [t2a_dryrun_response]
  ou [t2a_dryrun_error] en cas d'échec API
- Filet data_quality_warning (incohérence âge déclaré vs date naissance,
  motif vs diagnostic principal, décision vide) — filet, pas analyse
  exhaustive ; signale sans corriger (anonymisation v1 incertaine)
- Tableau récap stdout 9 colonnes + CSV scripts/bench_t2a_dryrun_<ts>.csv
- Stats agrégées : convergence durée X/N, convergence décision X/N
  mappés, liste détaillée des divergences avec pointeurs vers logs
- Recommandation auto : réécrire PROMPT 3 ou non selon convergence durée

Activation : T2A_DRYRUN=1 python scripts/bench_t2a_dryrun.py
Options : --ipp <IPP> (1 dossier), --skip-seed-check

Smoke test pré-commit (sans LLM) : parsing + dpi_raw + build_dpi_enriched
sur les 11 dossiers → 11/11 metadata complets, 0 parsing_warning,
durées calculées de 2.0h à 12.02h, décompo décisions terrain conforme
(7 Consultation + 1 Hosp + 1 UHCD + 1 Transfert + 1 Retour structure).

Brief complet : docs/handoffs/2026-05-12_brief_S1_build_dpi_enriched.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:55:57 +02:00
Dom
9872f4510c feat(t2a): build_dpi_enriched - extraction déterministe horaires + classifications cliniques
Préprocesseur Python qui injecte un bloc FAITS_CALCULÉS en tête du DPI
avant l'appel LLM, pour neutraliser l'hallucination de durée (bug "23h"
sur cas MOREL, confusion avec "depuis 23h" de l'Observ. IDE Urg).

Extrait depuis le bandeau Easily Assure et la Synthèse Urgences :
- âge (dateutil.relativedelta)
- date admission / sortie + durée passage (format humain + décimal)
- CCMU / GEMSA libellé complet (parser multi-ligne)
- priorité IAO, mode de venue / médicalisation / mode d'entrée
- diagnostic principal
- decision_terrain + orientation_terrain (metadata only, jamais injectés
  dans le prompt pour ne pas biaiser le LLM)

Retour tuple (dpi_enriched, metadata) pour permettre les garde-fous
serveur Python ↔ LLM au commit 2.

Robustesse :
- re.search 1re occurrence + WARNING si bandeau divergent multi-occurrences
- Synthèse Urgences priorité sur bandeau pour dates
- Valeur exigée sur même ligne que label (évite capture de section title)
- Cas négatif (horaires absents) → "NON CALCULABLE" + parsing_warnings
- Jamais de crash, retour tuple toujours valide

Tests : 4/4 verts (golden MOREL string + metadata, négatif sortie absente,
DPI vide). Pas de régression sur tests/integration/test_t2a_extract.py.

Brief complet : docs/handoffs/2026-05-12_brief_S1_build_dpi_enriched.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:49:49 +02:00
Dom
2eeaa806bb docs(handoff): clôture session 2026-05-09
Session de 6h consacrée au fix DETTE-006 (bug d'échelle pixel
grounding). Bilan : 2/5 commits fix faits (smart_resize + refactor
parser bbox_2d), 3/5 bloqués par découverte DETTE-010 (divergence
factor 28 vs 32 sur checkpoint Qwen3-VL-8B-Instruct, à instruire
demain matin).

Effets de bord positifs : registre dette technique créé
(14 entrées P1/P2/P3), investigation mémoire visuelle orpheline
documentée, infra clarifiée (vLLM absent, Transformers direct retenu,
checkpoint Qwen3-VL-8B fp16 téléchargé 17 GB).

Voir docs/handoffs/2026-05-09_session_audit.md pour détail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
demo-stable-2026-05-12
2026-05-09 15:53:26 +02:00
Dom
df5ad59330 docs(dette): MAJ DETTE-010 (config trouvé, divergences) + création DETTE-014 (smart_resize calé sur mauvaise référence)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:46:46 +02:00
Dom
bfbf0f9c3e refactor(grounding): centralise parser bbox_2d
Avant : 4 occurrences de parsing en cascade dans resolve_engine.py
(L840-885, L903-915, L2569-2580, ~110 lignes au total).

Après : centralisation dans core/grounding/bbox_parser.py avec
paramètre formats= permettant de filtrer les formats reconnus
selon le contrat sémantique de chaque site d'appel.

Préservation des contrats sémantiques (strict no-op) :
- Occ 1+2 (cascade principale) : tous formats (par défaut)
- Occ 3 (retry multi-image) : formats={"xy_json", "raw_array"}
  pour respecter le prompt qui impose {"x": NNN, "y": NNN} in pixels
- Occ 4 (_locate_popup_button) : formats={"bbox_2d"} pour respecter
  le prompt qui demande "bounding box"

Notes :
- Mini-bug Occ 3 retry multi-image (division systématique sans
  heuristique x>1, produisait coordonnées aberrantes ~0.0004 si
  VLM retournait déjà du pourcentage) corrigé incidemment via
  centralisation. Pas de régression possible (résultat précédent
  aberrant par construction).
- Occ 4 : bbox_2d strict 4-coords élargi à bbox_2d 2 ou 4 coords.
  Contrat sémantique "bounding box" respecté ; un point 2-coords
  interprété comme centre de bbox.

Tests : 26 cas dans test_bbox_parser.py (tous formats × cascade
+ filtre formats= + validated). 121 PASS / 0 FAIL sur le périmètre
refactor (5 fichiers ciblés).

Net : -96 lignes dans resolve_engine.py, +120 lignes module
+ 250 lignes tests.

refs DETTE-006 (étape 2/5 du fix smart_resize)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:30:25 +02:00
Dom
ecc5a233a7 docs(dette): création DETTE-013 env tests dev local
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:49:57 +02:00
Dom
293e54b4e6 docs(dette): création DETTE-012 (vLLM hors scope) + maj DETTE-010 (cible Transformers + AWQ)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:47:21 +02:00
Dom
0d7bcd18ac feat(grounding): module smart_resize officiel Qwen3-VL
Module pur core/grounding/smart_resize.py implémentant la formule
smart_resize officielle (transformers.qwen2_vl.image_processing_qwen2_vl,
utilisée par Qwen3VLProcessor pour les images via wrap Qwen2VLImageProcessor).

Helpers exposés : _round_by_factor, _floor_by_factor, _ceil_by_factor.
Constantes : FACTOR_DEFAULT=28, MIN_PIXELS_DEFAULT=3136,
MAX_PIXELS_DEFAULT=1_003_520, MAX_RATIO_DEFAULT=200.

Tests : tests/unit/test_smart_resize.py — 32 cas, 100% coverage sur le
module (mesure via coverage API directe, pytest-cov bloqué par bug cv2
préexistant tracé dans DETTE-011).

refs DETTE-006 (étape 1/5 du fix smart_resize)
refs DETTE-007 (création de la 3ème implémentation, à unifier post-démo)
refs DETTE-010 (vérif preprocessor_config.json checkpoint Qwen3-VL-8B
                bloquante avant Étape 2)
refs DETTE-011 (bug cv2 contourné pour mesure coverage)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:42:47 +02:00
Dom
4df1ba5779 docs(dette): création DETTE-011 bug cv2 Python 3.12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:42:36 +02:00
Dom
e9702b4df9 docs(dette): création DETTE-010 vérif preprocessor_config Qwen3-VL
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:08:08 +02:00
Dom
e0b47e4518 docs(refs): commit groupé docs de référence session 2026-05-08
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:32:52 +02:00
Dom
5dc20cc85b docs(dette): rectif mapping DETTE-005 + DETTE-008/009 + investigation mémoire visuelle orpheline
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:23:45 +02:00
Dom
88ed103de5 docs(dette): création registre dette technique + 7 entrées rétroactives
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:43:27 +02:00
Dom
194853cebb docs(handoff): clôture session 2026-05-08
3 commits du jour : pré-check OCR réactivé + instrumenté + bug
spatial documenté. Plan demain : fix smart_resize vLLM ciblé
selon MIGRATION_VLM_PLAN_2026-05-09.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:33:35 +02:00
Dom
626823d327 docs(bug): pré-check OCR spatialement aveugle - dette identifiée
Bug découvert pendant test live du 2026-05-08.
_text_match_fuzzy valide la présence du texte dans le crop (560×560 px)
sans vérifier sa position au point cliqué. Sur onglets serrés (3 px),
valide à tort les clics adjacents.

À fixer post-démo Kerella - Option B préférée
(bboxes EasyOCR + distance).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:31:56 +02:00
Dom
2e76b44ff3 feat(observability): log positif pré-check OCR pour traçabilité runtime
Avant : succès silencieux (seul rejet loggé)
Après : log INFO à chaque appel avec by_text, position, méthode,
observed, is_valid, latence

Permet de valider en runtime que le pré-check OCR tourne bien
sur les résolutions resolved=True (cf commit 731b5bcae).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:23:32 +02:00
Dom
731b5bcae2 fix(replay): réactivation pré-check OCR avec calibrage chirurgical
- Flag RPA_ENABLE_TEXT_PRECHECK défaut true (vs false pendant prépa démo)
- radius_px 200 → 280 (englobe textes longs type "Synthèse Urgences")
- min_token_ratio 0.60 → 0.50 (tolère onglets fragmentés par OCR)
- Commentaire historique restructuré avec procédure troubleshooting
- Docstring synchronisée avec valeur effective

Audit complet : docs/AUDIT_CONTROLES_DEBRANCHES_2026-05-08.md
Réactive contrôle #3 sur 5 identifiés (les 4 autres restent désactivés
pour aujourd'hui — décision chirurgicale 1 par 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:27:21 +02:00
Dom
8648e375fe docs(handoff): session audit 2026-05-08 - controles debranches 2026-05-08 11:37:40 +02:00
Dom
56e869c467 fix(replay): bug TypeError log + flag pré-check OCR off par défaut (démo GHT)
Some checks failed
tests / Lint (ruff + black) (push) Successful in 14s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
Diagnostic post-bench E2E (rapport docs/E2E_TEST_RUN_2026-05-08.md) :

1. BUG SILENCIEUX MAJEUR (api_stream.py:4549) — quand le pré-check OCR
   rejette, mon code de rejet hier soir met x_pct=None / y_pct=None.
   Le log structuré faisait result.get('x_pct', 0):.4f → None:.4f →
   TypeError → réponse "analysis_error" qui MASQUE le vrai motif
   "rejected_text_mismatch". Conséquence : pendant toute la session
   du 7 mai soir, les rejets pré-check ont été silencieusement
   transformés en erreurs analyse → cascade locale Léa V1 → clic au pif.
   Fix : `(result.get('x_pct') or 0):.4f` traite None | None | 0
   uniformément.

2. FLAG ENV pré-check OFF par défaut — le pré-check
   _validate_text_at_position introduit hier soir a 2 défauts
   identifiés par le bench E2E sur 8 click_anchor :
   * radius_px=200 trop petit pour les tabs à 2 tokens (Examens
     cliniques, Synthèse Urgences) — OCR voit un crop tronqué
     "Maquette POC ler en cours Codage Statistiques" qui n'inclut
     pas "Examens" → fuzzy match 1/2 = 50% < seuil 0.60 → REJET.
     À radius 300/400 le mot est inclus → match passe.
   * min_token_ratio=0.60 trop strict pour cibles 2 tokens.

   Solution démo : flag env RPA_ENABLE_TEXT_PRECHECK (défaut "false").
   Le pré-check est désactivé par défaut → retour au comportement
   stable d'avant-hier (hybrid_text_direct ≥ 0.80 utilisé direct,
   exemption drift préservée). Code et fonction _validate_text_at_position
   conservés en place pour reprise post-démo après calibrage radius
   adaptatif (≈ 0.17 × min(screen_w, screen_h)) et token_ratio descendu
   à 0.50.

   Pour ré-activer en dev/test : `RPA_ENABLE_TEXT_PRECHECK=true`
   dans .env.local ou env du service rpa-streaming.

Inclus aussi :
- docs/E2E_TEST_RUN_2026-05-08.md (rapport agent test E2E ~1700 mots)
- tests/e2e/urgence_aiva_demo_expected.yaml (tolérances re-écrites)
- tests/e2e/fixtures/urgence_aiva_demo/live/*.png (8 fixtures
  recapturées headless 1920x1080 pour itérer demain)
- _ocr_inventory.json + _run_resolve_results.json (raw runs)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:09:23 +02:00
Dom
f8dc3c3af4 docs(audit): rapport curateur mémoire Claude — santé index 7 mai 2026
Some checks failed
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
Audit exhaustif des 101 fichiers .md de ~/.claude/projects/-home-dom-ai-rpa-vision-v3/memory/.
Aucun fichier mémoire modifié — diagnostic seul, à valider par Dom.

Constats critiques :
- MEMORY.md = 273 lignes (limite chargement 200) → ~73 lignes
  silencieusement perdues à chaque démarrage de session
- ~50% des fichiers réels ne sont pas indexés dans MEMORY.md
- Référence cassée : MEMORY ligne 257 pointe vers
  feedback_pull_not_push.md qui n'existe pas
- 3 feedback NEW créés le 7 mai (non ajoutés à l'index) sont
  précisément les règles qui sécurisent la démo GHT jeudi 8 mai :
  * feedback_orphans_are_projections.md
  * feedback_verifier_avant_apres_clic.md
  * architecture_lea_v1_find_text_client.md

Risque concret : un Claude futur (sans ces feedback en mémoire active)
va reproposer les bourdes que Dom a explicitement nommées hier soir :
"re-capturer les ancres" et "nettoyer les modules orphelins".

Top 7 feedback proposés en TOP CRITICAL :
1. prendre_le_temps (DEVISE)
2. orphans_are_projections (NEW)
3. verifier_avant_apres_clic (NEW)
4. lea_v1_find_text_client (NEW architecture)
5. ollama_vs_transformers
6. no_rustine
7. anonymisation_stricte

Proposition réorganisation 4 zones :
- 🔥 TOP CRITICAL ~12 fichiers
- 📌 ACTIVE ~25 fichiers
- 📚 REFERENCE ~12 fichiers
- 🗄️ ARCHIVE ~50 fichiers

Compactage cible : MEMORY.md → 150 lignes (marge 50 avant
retrigger limite chargement).

4 décisions ouvertes pour Dom (cf rapport §8) :
1. feedback_pull_not_push.md — créer ou supprimer la référence
2. Valider l'archivage des ~45 fichiers proposés
3. Trancher 4 fichiers INCERTAIN (dashboard_config, data_extraction,
   objectif_6avril, actor_*)
4. Approuver 7 règles de gestion future (1 feedback = 1 violation
   observée, MEMORY ≤ 180 lignes, rotation sessions > 21j, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 05:11:08 +02:00
Dom
ca81850a20 docs(audit): rapport médecin DIM senior + TIM sur arbre décisionnel UHCD/Forfait
Some checks failed
tests / Lint (ruff + black) (push) Successful in 16s
tests / Tests unitaires (sans GPU) (push) Failing after 15s
tests / Tests sécurité (critique) (push) Has been skipped
Audit du cœur métier de la démo GHT Sud 95 (8 mai 2026), du point de vue
d'un médecin DIM senior qui se ferait challenger par le DSI Carvella.
Confronte : arbre officiel RPU UHCD IA.pptx (7 slides), code métier
agent_chat/urgences_orchestrator.py + core/llm/t2a_decision.py, prompts
LLM en place, 11 dossiers anonymisés data.js, bench Dom 18 modèles,
référentiels officiels (SFMU 2024, instructions DGOS, arrêtés 2021/2024
ATIH, recommandations IPAQSS).

Findings critiques (avant démo) :

1. Bug silencieux modèle — t2a_decision.py:28 met DEFAULT_MODEL=qwen2.5:7b
   (64 % accuracy au bench Dom) alors que gemma3:27b-cloud (73 %) est
   retenu par BENCH_T2A_DECISION_11DOSSIERS. Si T2A_MODEL pas posé via
   env, on tourne sur le mauvais modèle. 9 points d'accuracy laissés
   sur la table.

2. Règle de combinaison incorrecte dans le prompt — code dit "au moins
   2 sur 3 ⇒ REQUALIFICATION" alors que l'arbre PPTX d'Eaubonne dit
   "si oui aux 3 critères". Cause probable des faux positifs UHCD du
   bench (25003284, 25056615). Quick win = passer à 3/3.

3. Trous métier dans le prompt : aucune mention CCMU, GEMSA, durée,
   mode de sortie, type de forfait précis (SU2/PE2/Standard). C'est
   exactement où se loge le ROI 100k€/mois. 5 quick wins prompt
   rédigés prêts à coller dans §E.4 du rapport.

4. Trois dossiers à NE PAS montrer en démo (25056615, 25151530, 25003475,
   25048485) — trop ambigus, hallucinations LLM, structure non tranchée.

5. Trois dossiers à mettre en avant (25003451 SU2 plaie 2h, 25010621
   PE2 laryngite, 25003364 UHCD pneumo SLA) — décisions justes,
   justifications béton.

Argumentaire pré-démo : 9 questions/réponses face à Carvella
(instructions DGOS, SFMU, cumul SU2+PE2, hallucination LLM, ROI 100k€).

Roadmap post-démo pour Amina : bench étendu 50-100 dossiers + 3
inférences/dossier, fine-tune t2a-gemma3-27b, distinction forfaits
fine, module ATIH-aware, couverture pédia/géria/psy, sortie contre
avis, transferts.

Note : aucun changement de code dans ce commit. Rapport seul. Les
quick wins identifiés (3/3, modèle par défaut, prompts enrichis)
sont à appliquer demain matin avec validation Dom + Amina.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:21:13 +02:00
Dom
35fd6cf4c5 test(e2e): harness replay reproductible — mock client Léa V1 contre serveur réel
Some checks failed
tests / Lint (ruff + black) (push) Successful in 14s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
Réduit le cycle debug d'un workflow de 1-2 min (replay manuel via
Windows + Léa V1 + maquette) à ~2-5s (mock client Linux contre
serveur de streaming localhost:5005). 30-60× plus rapide.

Architecture :
- tools/test_replay_e2e.py — harness CLI (~580 lignes), reproduit la
  chaîne réelle : VWB /api/v3/execute-windows → streaming /replay/raw
  → boucle /replay/next côté harness avec resolve_target sur un
  screenshot fixture → POST /replay/result. Pas de modification serveur.
- tests/e2e/test_urgence_aiva_demo.py — wrapper pytest (smoke).
- tests/e2e/urgence_aiva_demo_expected.yaml — référence générée par
  --export-expected, pour comparaison régression auto.
- pytest.ini — ajout du marqueur e2e.

Usage :
    python tools/test_replay_e2e.py --execution-mode autonomous --max-iter 120 --verbose
    python tools/test_replay_e2e.py --single-step 8 --shot <heartbeat>.png
    python tools/test_replay_e2e.py --expected tests/e2e/urgence_aiva_demo_expected.yaml
    pytest tests/e2e -v -m e2e

Sortie : tableau Markdown step × méthode × score × pos × status × diag.

Limitations connues (extensions post-démo) :
- Une seule fixture screenshot pour tout le replay → click_anchor réalistes
  échouent dès qu'on dépasse l'écran fixture. Carte step_id → fixture à venir.
- extract_text/table/t2a_decision exécutés côté serveur, observables mais
  pas modifiables.
- Pas de simulation screenshot_after → ReplayVerifier (Critic VLM) ne tourne pas.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:11:07 +02:00
Dom
7847a0e829 feat(agent_v1): toast paused supervisée Tkinter + Plan B + threshold FIND-TEXT 0.75
Some checks failed
tests / Lint (ruff + black) (push) Successful in 16s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
Démo GHT 8 mai 2026 — Dom utilise UNIQUEMENT Léa V1 sur Windows pendant
la démo (pas le frontend VWB Linux), donc les pause_message du serveur
doivent être visuellement évidents sur l'écran Windows. Modifications
client validées par Dom + redéployées via SCP (procédure 2026-04-28).

1. ui/paused_toast.py (NEW) — Toast Tkinter custom autonome :
   Toplevel topmost overrideredirect, fond bleu Léa (#2563EB), 380px,
   haut-droite, auto-close 15s, click-to-close. Re-pin -topmost à
   100/500/2000 ms (Windows démet le flag quand le focus part). Rate
   limit 3s sur message identique. Aucune dépendance externe (tkinter
   stdlib uniquement). Thread-safe : root.after si Tk root existe,
   sinon Tk dédié dans un daemon thread. Remplace plyer qui s'avère
   silencieux sur Windows 11 (Focus Assist + manque app-id COM).

2. ui/chat_window.py — _add_paused_bubble force la visibilité :
   La fenêtre Léa démarrait avec root.withdraw() — la bulle paused
   était bien rendue mais invisible. Ajout deiconify+lift+focus_force
   avant render, plus appel à show_paused_toast en complément.

3. ui/notifications.py — niveau BLOCAGE déclenche aussi le toast :
   Quand notify_message reçoit un MessageUtilisateur.BLOCAGE (cible
   non trouvée, mode apprentissage, fenêtre incorrecte), appelle
   show_paused_toast en plus de plyer. Couvre la branche supervision
   client (executor.py:1012) qui ne passe pas par Plan B serveur.

4. core/executor.py — Plan B replay_paused (lignes 1812-1850) :
   Intercepte data["replay_paused"]=True dans la réponse /replay/next,
   appelle chat_window._add_paused_bubble si _chat_window_ref défini,
   sinon fallback notifier.notify. Idempotence via _last_pause_msg_shown
   pour ne pas spammer (1 toast par (replay_id, message) unique).
   Threshold FIND-TEXT _find_text_on_screen : 0.50 → 0.75 pour rejeter
   les faux positifs (placeholders italiques, tabs voisins) et tomber
   en mode apprentissage humain plutôt qu'un clic au pif.

5. main.py — Wiring ChatWindow → Executor pour Plan B.

6. tools/test_lea_toast.py + ui/_test_paused_toast.py (NEW) — Scripts
   de test isolé pour validation visuelle rapide sans relancer un
   replay complet (commande dans les docstrings).

Validé visuellement sur DESKTOP-58D5CAC. Toasts apparaissent en haut-
droite, fond bleu, auto-close 15s. Test isolé Dom : 3 toasts successifs
visibles sans accroc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:03:51 +02:00
Dom
40440f1ca0 fix(replay): cure régression b584bbabc — fallback recorded_coords aveugle
Trois changements complémentaires dans la cascade de résolution serveur,
finis ce soir 7 mai pour la démo GHT 8 mai. Restaure le comportement strict
d'avril 2026 (workflow qui passait 20 fois d'affilée sans incident).

1. resolve_engine.py — _validate_resolution_quality (lignes 2255-2289) :
   Le commit b584bbabc du 1er mai 2026 ("fix(stream): démo UHCD") avait
   transformé le rejet strict (resolved=False, method="rejected_drift_*")
   en fallback aveugle (resolved=True, method="fallback_recorded_coords",
   coords du record). Symptôme observé : Léa cliquait sur "Dossier en
   cours" du menu au lieu de "Synthèse Urgences" du tab — le VLM Quick
   Find Ollama hallucinait à (0.526, 0.918), drift dépassé, fallback
   ratait. Restauré : resolved=False explicite, le client passe en
   pause supervisée comme prévu (philosophie échec = apprentissage).

2. resolve_engine.py — exemption high-confidence élargie :
   L'exemption drift>0.20 IGNORÉ ne couvrait que template_matching ≥ 0.95
   (commit 35b27ae49 du 2 mai). Étendue à hybrid_text_direct ≥ 0.80 :
   un OCR direct qui trouve le texte cible exact à score 0.80+ est aussi
   sûr qu'un template à 0.95 — la position est sémantiquement vraie,
   le drift reflète juste un changement de layout (résolution écran,
   refonte UI, scroll), pas une erreur de résolution.

3. resolve_engine.py + api_stream.py — pré-check OCR sémantique :
   Nouvelle fonction _validate_text_at_position (singleton EasyOCR fr+en,
   crop 200px autour de la coord résolue, fuzzy match 60% des tokens
   ≥3 caractères de l'expected_text). Câblée dans api_stream.py juste
   après _validate_resolution_quality. Si le by_text attendu n'est PAS
   présent dans la zone autour de la coord résolue → resolved=False
   method="rejected_text_mismatch" → pause supervisée.

Pattern Verification-Aware Planning (state of the art 2026 — voir
recommandations agent archéologue + agent SOTA review) : le serveur
ne renvoie une coord que s'il est sémantiquement sûr du résultat.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:03:18 +02:00
Dom
7233df2bb9 fix(replay): câblage execution_mode supervised + seuil large fallback heartbeat
Deux corrections liées au scenario démo Urgence GHT (workflow lecture
multi-onglets + t2a_decision + pause_for_human + saisies dans Codage) :

1. Mode supervised propagé jusqu'au pipeline replay
---------------------------------------------------

Symptôme constaté ce 7 mai : Léa lit les onglets, t2a_decision tourne
(variable `dec` présente avec decision="FORFAIT_URGENCE"), mais la
pause_for_human est SKIPPÉE silencieusement et les saisies type_text
s'enchaînent dans le mauvais écran.

Cause : api_stream.py:2140 passait `params={}` codé en dur lors de la
création du replay_state. Conséquence : le code en aval qui lit
`replay_state.params.execution_mode` (api_stream.py:2964) avait toujours
le défaut "autonomous" → branche QW4 :

    # Mode autonome sans safety_checks → skip (comportement legacy)
    logger.info("pause_for_human ignorée (mode autonome)")

Modifications :
- RawReplayRequest gagne un champ `params: Optional[Dict[str, Any]]`
- start_raw_replay propage `request.params or {}` à _create_replay_state
- dag_execute.execute_windows force par défaut
  `data['params']['execution_mode'] = 'supervised'` quand le frontend
  ne précise rien (cas démo VWB → Windows). Override possible.

Conséquence : la pause_for_human du workflow Urgence déclenche bien la
PauseDialog VWB ("Décision : {{dec.decision_court}}"). Le médecin valide
ou annule avant que les saisies type_text ne s'exécutent dans Codage.

Note pour la démo réelle (post-aujourd'hui) : le scénario crédible
veut que Léa soit déclenchée depuis SON chat (port 5004), pas depuis
VWB. C'est un autre commit à venir — pour l'instant VWB suffit pour
le développement (cf. handoff session).

2. Seuil détection image tronquée élargi
----------------------------------------

Le seuil initial (height < 200 OR width < 400) ne capturait que les
cas extrêmes 2560x60 / 600x72. Mais le client envoie aussi 622x856
(Edge en fenêtre réduite ?) qui passait sous le radar. Élargi à
height < 800 OR width < 1200 — un écran moderne fait toujours ≥
1920x1080, donc le seuil est sain.

Sans ce fallback élargi, _resolve_target_sync recevait une image
trop petite pour matcher l'anchor → cascade VLM hallucinante.
2026-05-07 10:34:29 +02:00
Dom
f62fda575f fix(stream): /resolve_target — fallback heartbeat full si image client tronquée
Bug client constaté ce 2026-05-07 sur PC Windows 192.168.1.11 (agent V1) :
mss.monitors[1] retourne parfois une image tronquée type 2560x60, 2560x108,
600x72 — possiblement la barre des tâches Windows confondue avec un monitor,
ou un état mss corrompu. Reproduit même PC en mono physique. Cause exacte
non isolée côté client.

Sans cette image, _resolve_target_sync ne peut rien résoudre :
- Template matching échoue (anchor 104x31 vs image 600x72)
- OCR direct ne trouve pas la cible (texte hors de l'image tronquée)
- VLM Quick Find hallucine systématiquement la même position
- Fallback recorded_coords clique au mauvais endroit

Conséquence reproduite hier soir : "Léa clique partout au pif"
(cf. session_20260506_handoff_v2.md).

Filet de sécurité côté serveur : si l'image reçue est anormalement
tronquée (height < 200 ou width < 400), le serveur la remplace par le
dernier heartbeat full screen avant la cascade _resolve_target_sync.

Sources de fallback dans l'ordre :
1. _last_heartbeat (mémoire, peuplé par /stream/image en runtime)
2. Scan disque data/training/live_sessions/*/bg_*/shots/heartbeat_*.png
   (utile après restart serveur ou si l'agent V1 ne polle pas)

Validé en isolation : image tronquée 600x60 → fallback heartbeat 2560x1600
→ template matching score 0.999 → coords (0.0312, 0.3500) = exactement
la position de l'IPP cible '25003284' en première ligne d'Easily Assure.

Bug client à traiter post-démo. Le fallback heartbeat reste utile en
roadmap autonome (résilience aux états mss transitoires).

Note : également retiré un import os local redondant dans le finally
(masquait la variable globale et provoquait UnboundLocalError dans
le scope du bloc fallback).
2026-05-07 09:31:07 +02:00
Dom
22c0a2ba61 revert: désactiver self-healing Win+D auto (cercle vicieux)
Revert effectif du commit c969f93a2.

Le Win+D auto au retry 1 produit un cercle vicieux quand combiné avec
le VLM-first qui hallucine systématiquement (positions répétitives
type 0.529/0.874 avec confidence 0.93 sans justification) :

  click rate (cible mal localisée par VLM) → no_screen_change
  → Win+D auto → minimise Easily Assure
  → retry click → cible plus visible (Easily masquée par Win+D)
  → no_screen_change → Win+D encore → boucle infernale

Reproduit ce 2026-05-06 sur le workflow Urgence : 10 Win+D dispatchés
en moins de 2 minutes. Régression majeure ressentie par Dom :
"clic partout au pif, aucune action contrôlée".

L'idée du self-healing par gesture reste valide mais demande :
1. un déclenchement plus sélectif (genre overlay/popup détecté
   visuellement, pas no_screen_change générique)
2. ou un Alt+Tab plutôt que Win+D (fait passer la fenêtre arrière
   sans minimiser l'app cible)
3. ou une vraie analyse "y a-t-il une fenêtre qui obstrue ma cible"
   avant de décider du gesture

À retravailler post-démo avec un vrai détecteur d'obstruction.
2026-05-06 20:31:31 +02:00
Dom
6fdedbfe9d fix(vwb): execute-windows route vers la machine la plus active (pas alphabétique)
Quand le frontend ne passe pas de machine_id explicite, le backend VWB
auto-sélectionne une machine Windows en interrogeant /api/v1/traces/
stream/machines. Le code prenait la première de la liste sans tri, donc
l'ordre dépendait de l'ordre arbitraire renvoyé par le streaming server.

Conséquence reproduite ce 2026-05-06 : un replay du workflow Urgence a
été dispatché vers DESKTOP-ST3VBSD_windows alors que l'agent V1 actif
polait depuis DESKTOP-58D5CAC_windows. /replay/next ne dispatchait
aucune action puisque state.machine_id != polling_machine_id.
Symptôme côté Dom : "rien ne se passe sur Windows".

Correction : tri explicite par last_activity desc avant sélection.
La machine retenue est désormais celle qui a heartbeaté le plus
récemment (= celle qui POLLE actuellement le serveur).

Le workflow.machine_id (machine d'origine d'enregistrement) reste
distinct de la cible d'exécution : un workflow enregistré sur PC A
peut être rejoué sur PC B grâce au pipeline 100% visuel qui recalcule
anchors et coordonnées selon la résolution courante. C'était la
vraie intention architecturale, masquée par le bug de tri.
2026-05-06 20:23:44 +02:00
Dom
c969f93a23 fix(replay): self-healing Win+D auto au retry 1 (verification_failed)
Audit project-quality-guardian (2026-05-06) Cas #2 : le mécanisme
qui invoquait gesture_catalog.win_minimize_all (Win+D) en cas
d'échec de grounding a été archivé le 24/04 dans
_archive/dead_code_20260424/core/visual/rpa_integration_manager.py
(_attempt_self_healing_resolution). Le catalogue
agent_chat/gesture_catalog.py:84 reste intact mais orphelin —
aucun caller actif.

Conséquence : quand une fenêtre/popup obstrue la cible, Léa
retente N fois la même action ratée puis pose une pause supervisée,
alors qu'un Win+D ("Afficher le bureau") règle souvent le problème
en 200 ms.

L'audit proposait observe_reason_act.py mais ce module est utilisé
uniquement par /execute/instruction (lui aussi sans client actif,
Cas #10). Le bon point d'insertion dans le pipeline replay actif
est _schedule_retry (replay_engine.py) — la fonction qui construit
la liste d'actions à réinjecter en tête de queue avant chaque retry.

Modification :

Au next_retry == 1 ET reason in ("verification_failed",
"no_screen_change"), insertion en tête de queue de :

  1. Action key_combo {keys: ["super", "d"]} (format reconnu par
     agent_v1/core/executor.py:1151), tagué
     _recovery_gesture: "win_minimize_all" pour audit.
  2. Wait 500 ms pour laisser l'OS terminer l'animation Win+D.
  3. Le retry de l'action originale.

Au retry 2 et au-delà, comportement inchangé (wait 2s + retry).

Tests : 27/27 baseline sprint QW verts.
2026-05-06 19:27:16 +02:00
Dom
1cbec2806e fix(resolve): rebrancher hybrid_text_direct dans _resolve_target_sync
Audit project-quality-guardian (2026-05-06) : la fonction
_resolve_by_ocr_text (resolve_engine.py:1447) existait déjà mais
n'était appelée QUE depuis _resolve_with_precompiled_order (V4),
endpoint sans client côté frontend (Cas #5 du même audit). La
cascade legacy _resolve_target_sync sautait directement d'étape 0
(grounding-window) → étape 0' (template icônes) → étape 1 (VLM
Quick Find) sans tenter l'OCR direct.

Conséquence reproduite ce 2026-05-06 sur le workflow Urgence :
chaque action visuelle avec by_text payait 2-23 s de VLM Quick
Find (ui-tars-1.5-7b-q8_0 sur Ollama) au lieu de <500 ms d'OCR
direct, total replay > 10 min vs quelques secondes attendues.
Constat utilisateur : "habituellement on est plutôt à quelques
secondes". Régression silencieuse.

Modification :

Étape 0.5 ajoutée entre l'étape 0' (template icônes) et l'étape 1
(VLM Quick Find). Si by_text_strict est non vide, appel à
_resolve_by_ocr_text — fonction docTR existante, cache singleton
_V4_OCR_PREDICTOR, score 1.0 si match exact, 0.9 si mot exact,
0.8 si contenu. Seuil de retour : 0.80 (cohérent avec
_RESOLUTION_MIN_SCORES["hybrid_text_direct"]).

Le method retourné est rebadgé "hybrid_text_direct" pour cohérence
avec :
- _RESOLUTION_MIN_SCORES (seuil 0.80, ligne 2092)
- agent_v0/agent_v1/core/executor.py:1534 (client Windows)
- logs Learning historiques ([hybrid_text_direct])

Tests : 39/39 sprint QW + grounding/resolver verts.
2026-05-06 19:24:53 +02:00
Dom
864530c851 fix(stream): _async_replay_lock helper + 17 endpoints async non-bloquants
Suite directe des commits 35b27ae49 (lock async sur /replay/next) et
87dbe8c5f (get_replay_status non-bloquant) qui n'avaient traité que
2 endpoints sur les 19 utilisant _replay_lock dans api_stream.py.

Reproduit aujourd'hui en pré-démo : un replay urgences a réussi
extract_text + t2a_decision (50s, OK), puis a hang sur l'action
suivante. start_raw_replay (POST /replay) du nouveau replay a tenté
`with _replay_lock:` synchrone à la ligne 2085 → MainThread asyncio
gelé → tous les endpoints derrière. Stack via py-spy confirmée.

Le pattern systémique : 17 sites `with _replay_lock:` synchrones
dans des handlers `async def` (start_replay, start_raw_replay,
replay_from_session, enqueue_single_action, launch_replay_from_plan,
get_next_action [×3], report_action_result [×5], register_error_callback,
list_replays, resume_replay, cancel_replay). Chacun gèle l'event
loop FastAPI dès qu'un autre thread tient le lock.

Modifications :

1. Helper _async_replay_lock(timeout=4.5) (api_stream.py:516).
   Acquire via run_in_executor (event loop libre pendant l'attente),
   timeout 4.5s puis HTTPException 503 plutôt que gel infini.
   Sémantique acquire+release identique au `with` synchrone.

2. Remplacement automatisé des 17 sites async :
   `with _replay_lock:` → `async with _async_replay_lock():`
   2 sites sync intentionnellement préservés (cleanup loop ligne 689,
   chat_status_provider ligne 5048 — pas dans des handlers async).

3. Import contextlib ajouté en haut du fichier.

Tests : 27/27 baseline sprint QW verts, /health 200 (3ms),
/replays 200 (2ms — endpoint qui utilise le nouveau helper).
2026-05-06 18:06:42 +02:00
Dom
d1ebf62217 fix(infra): durcissement headless — pyautogui robuste + cleanup .service
Suite à la mise à jour système qui a basculé Dom de Xorg vers Wayland,
les 4 services systemd côté serveur partaient en boucle restart :
pyautogui levait DisplayConnectionError / KeyError(DISPLAY) à l'import
dans 3 modules, mais l'except n'attrapait qu'ImportError → crash fatal.

Le contournement « ajouter DISPLAY=:1 + XAUTHORITY=/run/user/1000/gdm/
Xauthority dans .service » introduit fin avril était fragile : chemin
invalide en Wayland (Mutter utilise un Xauthority à suffixe aléatoire
qui change à chaque login). Le bon fix est de rendre les imports
pyautogui robustes — le serveur n'a aucun usage légitime de pyautogui,
c'est le client Agent V1 Windows qui pilote souris/clavier.

Modifications :

1. Élargi `except ImportError` → `except Exception` pour pyautogui :
   - agent_chat/autonomous_planner.py
   - core/execution/input_handler.py
   - core/execution/observe_reason_act.py
   (action_executor.py était déjà robuste avec except Exception.)

2. Retiré DISPLAY/XAUTHORITY des 4 .service (rustines) :
   - rpa-streaming.service
   - rpa-vision-v3-{api,worker,dashboard}.service
   Block grounding (RPA_GROUNDING_SOCKET) préservé (initiative
   séparée de partage VRAM, in-flight).

PYAUTOGUI_AVAILABLE=False est désormais attendu côté serveur Linux ;
les chemins aval (action_executor, autonomous_planner) gèrent déjà
ce cas via des branches "actions simulées" / "pyautogui non disponible".

Prépare la roadmap autonome (Léa daemon Linux + VM Windows) qui
tournera headless via systemd au boot, sans dépendre d'aucune
session graphique active.

Tests : 27/27 baseline sprint QW verts.
2026-05-06 17:19:18 +02:00
Dom
87dbe8c5ff fix(stream): get_replay_status non-bloquant + bornage actions serveur
Suite du commit 35b27ae49 (lock async sur /replay/next) qui n'avait
traité que la moitié du problème. Le sprint QW4 (commit f5c33477f)
a recâblé le polling frontend PauseDialog vers /replay/{replay_id} →
get_replay_status, qui gardait un `with _replay_lock:` synchrone.
Conséquence : dès qu'une action serveur (extract_text/extract_table/
t2a_decision) tient le lock, l'event loop FastAPI gèle entièrement
(heartbeats Windows, polls replay/next, get_replay_status, tout).

Reproduit aujourd'hui en pré-démo : un replay urgences a fait
extract_text → la queue suivante a tenu le lock → polling VWB sur
get_replay_status a bloqué le MainThread asyncio → 23 minutes de
gel total (py-spy a confirmé MainThread sur api_stream.py:4117).

Modifications :

1. get_replay_status : acquire timeboxé 0.5s via run_in_executor
   (même pattern que /replay/next ligne 2815). Si le lock est tenu,
   retour immédiat {status: "busy"} → le frontend retentera dans 1s.
   Aucun cas où ce poll bloque l'event loop.

2. Actions serveur lignes 2994/3000/3006 : enveloppées dans
   asyncio.wait_for(timeout=180). Borne dure pour qu'un hang
   d'EasyOCR / Ollama / I/O ne tienne plus jamais le lock
   indéfiniment. TimeoutError est rattrapée par l'except Exception
   existant → queue.pop(0) → on continue.

Tests : 27/27 baseline sprint QW verts.
2026-05-06 17:19:05 +02:00
Dom
0a02a6ec9c feat(qw4): bench rigoureux LLM safety_checks → gemma4:latest par défaut
Some checks failed
tests / Lint (ruff + black) (push) Successful in 15s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped
Bench 5 modèles × 5 scénarios × cold+warm sur RTX 5070 :
- gemma4:latest : warm 2.9s, JSON 92%, détection 46% → gagnant
- qwen2.5vl:7b : warm 6.6s, détection 23% (trop lent)
- qwen2.5vl:3b : warm 2.0s, détection 8% (vérifie pour vérifier)
- medgemma:4b : warm 0.5s, détection 0% (refuse de signaler) → mauvais
  défaut initial, corrigé
- qwen3-vl:8b : 0% JSON valide (ignore format=json Ollama) → écarté

Modifications safety_checks_provider.py :
- RPA_SAFETY_CHECKS_LLM_MODEL défaut: medgemma:4b → gemma4:latest
- RPA_SAFETY_CHECKS_LLM_TIMEOUT_S défaut: 5 → 7 (warm 2.9s + marge)

Doc complète : docs/BENCH_SAFETY_CHECKS_2026-05-06.md
Script : tools/bench_safety_checks_models.py (reproductible, ~10-15 min)

Limite assumée : 46% de détection. À présenter en démo comme aide médecin,
pas certification. Amélioration V2 = prompt plus dirigé sur champs à vérifier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 09:23:09 +02:00
Dom
83be93e121 chore(qw): cleanup post-review (préfixes BUS, événements monitor, import io)
Some checks failed
tests / Lint (ruff + black) (push) Successful in 14s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped
- safety_checks_provider : tous les logger.warning d'échec LLM préfixés
  [BUS] lea:safety_checks_llm_failed avec une raison spécifique
  (exception, http_status, timeout, network, json_decode).
- monitor_router : émission [BUS] lea:monitor_invalid_index si l'index
  explicite passé dans l'action est hors limites de monitors_geometry,
  et [BUS] lea:monitor_unavailable si focus actif demandé mais introuvable.
  Ces deux events permettent au bus de tracer chaque fallback de la cascade
  de routage QW1.
- safety_checks_provider : import io supprimé (inutilisé).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:08:22 +02:00
Dom
f5c33477f0 fix(qw4): câblage polling frontend → streaming pour PauseDialog
Avant ce fix, le frontend VWB ne savait pas qu'un replay Agent V1 (Windows)
était en pause supervisée : le seul polling (App.tsx) interrogeait
/execute/status (exécution locale Linux) et n'avait jamais l'info
safety_checks / pause_message du replay distant.

Côté backend (dag_execute.py) :
- ajout du proxy GET /api/v3/replay/state/<replay_id> qui forward vers
  /api/v1/traces/stream/replay/<id> avec Bearer token.

Côté frontend :
- ExecutionControls : nouvelle prop onWindowsReplayStarted, appelée avec
  le replay_id retourné par /api/v3/execute-windows.
- App.tsx : nouveau state streamingReplayId + useEffect qui poll
  /api/v3/replay/state/<id> toutes les secondes et fusionne status,
  pause_message, pause_reason, safety_checks dans appState.execution.
  Le PauseDialog existant s'affiche donc automatiquement quand
  status = paused_need_help.

Le polling s'arrête quand le replay est completed/error/cancelled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:06:20 +02:00
Dom
b1a3aa16f1 fix(qw1): enrichir heartbeat Windows avec monitor_index + monitors_geometry
Avant ce fix, le _heartbeat_loop côté Agent V1 deploy Windows
n'enrichissait pas son payload, donc QW1 multi-écran ne s'activait sur Windows
que via les events window_capture (déclenchés par les clics), pas en continu.

La source agent_v0/agent_v1/main.py portait déjà l'enrichissement (commit 2d71e2a24)
mais le snapshot deploy/windows_client/agent_v1/main.py n'avait pas été synchronisé.

Désormais chaque heartbeat porte monitor_index + monitors_geometry, le serveur
peut donc résoudre l'écran cible en permanence, même sans clic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:02:11 +02:00
Dom
0bcfddbbc4 docs(qw): plan de smoke tests manuels pour validation 2026-05-06
Some checks failed
tests / Lint (ruff + black) (push) Successful in 15s
tests / Tests unitaires (sans GPU) (push) Failing after 15s
tests / Tests sécurité (critique) (push) Has been skipped
Plan exécutable seul par Dom : 9 sections (préflight, QW1 mono/multi-écran,
QW2 boucle, QW4 backward/déclaratif/medical_critical, bus events, kill-switches,
rollback) avec checklist OK/KO et procédures d'urgence en pleine démo.

Validation pour démo GHT (1ère sem mai 2026).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:01:21 +02:00
Dom
aa47172f0f docs(qw): synthèse de livraison QW suite mai 2026
Some checks failed
tests / Lint (ruff + black) (push) Successful in 14s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
Doc condensée des 3 quick wins livrés (QW1 multi-écrans, QW2 LoopDetector,
QW4 safety_checks hybrides) avec :
- procédures kill-switch et rollback
- table des env vars
- smoke tests manuels à effectuer avant démo GHT
- statut composant par composant

Pointe vers spec et plan d'exécution complets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:48:26 +02:00
Dom
65da557310 feat(qw4): hook safety_checks_provider + extension /replay/resume avec acquittements
Some checks failed
tests / Lint (ruff + black) (push) Successful in 16s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
replay_state enrichi de safety_checks, checks_acknowledged, pause_reason,
pause_payload (audit trail).

Branche supervisée pause_for_human :
- appel build_pause_payload() avant bascule paused_need_help
- log [BUS] lea:safety_checks_generated (count, sources)
- fallback safe sur exception (pause sans checks plutôt que crash)
- déclenchement si safety_level/safety_checks déclarés OU execution_mode != autonomous
- sinon comportement legacy (skip silencieux)

POST /replay/resume :
- accepte body { acknowledged_check_ids: [...] }
- vérifie tous les checks required acquittés, sinon 400 required_checks_missing
- stocke checks_acknowledged comme audit trail
- nettoie safety_checks/pause_payload après reprise

Proxy VWB /api/v3/replay/resume → streaming /replay/{id}/resume (forward bearer
token + acknowledged_check_ids).

Backward 100% : workflows sans safety_checks → resume sans acquittement requis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:45:22 +02:00
Dom
af13cd80ff feat(vwb): PauseDialog + ChecklistPanel + extension PropertiesPanel pour safety_checks
PauseDialog (composant nouveau) :
- 2 modes selon payload : bulle simple legacy si safety_checks vide,
  ChecklistPanel sinon
- Continuer désactivé tant que required non cochés
- Badge [obligatoire] et [Léa] (avec evidence en tooltip)
- POST /api/v3/replay/resume avec acknowledged_check_ids quand replay_id
  présent, fallback api.resumeExecution() pour la voie locale

types.ts : SafetyCheck, SafetyLevel, extension Execution
(pause_reason, pause_message, safety_checks, replay_id, status
'paused_need_help'). Action pause_for_human enrichie de safety_level
et safety_checks dans le catalogue ACTIONS.

PropertiesPanel : éditeur safety_level (dropdown standard/medical_critical)
+ liste éditable de safety_checks (id/label/required + ajout/suppression).

App.tsx : rendu conditionnel du PauseDialog en overlay quand
status == paused_need_help, ou paused avec safety_checks. Backward 100% :
workflows existants sans safety_checks affichent la bulle legacy.

CSS : .pause-dialog-overlay/.pause-dialog-checks/.checklist-panel/
.check-item/.badge-required/.badge-lea/.check-editor-row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:33:04 +02:00
Dom
7c6945171e feat(qw4): SafetyChecksProvider hybride déclaratif + LLM contextuel
build_pause_payload(action, state, last_screenshot) → PausePayload
- Toujours inclure les checks déclaratifs (workflow.parameters.safety_checks)
- Si safety_level=medical_critical ET RPA_SAFETY_CHECKS_LLM_ENABLED=1 :
    appel LLM (medgemma:4b par défaut) en format=json strict, timeout 5s,
    max 3 checks ajoutés (configurables via env vars)
- Tous les chemins d'erreur (timeout, HTTP, JSON parse, exception) loggent
  et retournent [] (fallback safe : déclaratifs seuls)

Tests : 7 cas (déclaratif seul, hybride OK, timeout, LLM invalide,
kill-switch, max_checks, déclaratif vide).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:29:38 +02:00
Dom
ca0b436a61 feat(qw2): hook LoopDetector dans api_stream + extension replay_state
Some checks failed
tests / Lint (ruff + black) (push) Successful in 15s
tests / Tests unitaires (sans GPU) (push) Failing after 17s
tests / Tests sécurité (critique) (push) Has been skipped
replay_state enrichi de _screenshot_history (5 dernières images PIL) et
_action_history (5 dernières signatures action).

report_action_result :
- met à jour les deux anneaux après chaque action
- évalue le LoopDetector (singleton lazy avec _clip_embedder serveur)
- si detected → bascule paused_need_help avec pause_reason="loop_detected"
  et bus event lea:loop_detected (signal + evidence)

Tous les chemins d'erreur (embedder absent, OOM, exception) loggent et
laissent le replay continuer — aucun blocage par la couche détection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:25:04 +02:00
Dom
fc01afa59c fix(qw1): bus event lea:monitor_routed + cablage offset côté executor Agent V1
Cleanup post-review QW1 :
- Émission bus lea:monitor_routed dans /replay/next (idx, source, replay_id, action_id, offset, wh)
  via logger.info "[BUS] lea:monitor_routed ..." (le serveur streaming n'a pas
  de SocketIO local, agent_chat émet déjà lea:* sur 5004 ; ici on logge en INFO
  bien lisible, prêt pour un parser/pont futur)
- Executor Agent V1 (deploy/windows_client) lit action.monitor_resolution.{offset_x, offset_y, idx}
  et applique l'offset aux coords absolues du clic/type/scroll/popup quand idx >= 0
- composite_fallback (idx=-1) : pas d'offset appliqué (backward compat mono-écran)
- Log INFO "QW1 monitor cible idx=N source=X offset=(dx,dy) — appliqué aux coords"
  émis une fois par action quand un offset non nul s'applique

Tests : baseline 95 passed (e2e + phase0_integration + stream_processor + monitor_router + grounding_offset)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:16:06 +02:00
Dom
2a51a844b9 feat(qw2): LoopDetector composite (screen_static + action_repeat + retry)
Module isolé, 3 signaux indépendants :
- screen_static : CLIP similarity > 0.99 sur N captures consécutives
- action_repeat : N actions identiques (type+coords)
- retry_threshold : retried_actions >= seuil

Premier signal positif → LoopVerdict.detected=True (caller responsable de
la bascule en paused_need_help).

Configurable env vars : RPA_LOOP_DETECTOR_ENABLED (kill-switch),
RPA_LOOP_SCREEN_STATIC_N/THRESHOLD, RPA_LOOP_ACTION_REPEAT_N,
RPA_LOOP_RETRY_THRESHOLD.

Tests : 8 cas (chaque signal isolé, kill-switch, embedder absent, exception).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:09:43 +02:00
Dom
2d71e2a249 feat(qw1): enrichissement Agent V1 (monitor_index + monitors_geometry) + hook serveur
Some checks failed
tests / Lint (ruff + black) (push) Successful in 16s
tests / Tests unitaires (sans GPU) (push) Failing after 15s
tests / Tests sécurité (critique) (push) Has been skipped
Côté client Agent V1 :
- helpers _get_monitors_geometry() / _get_active_monitor_index() via screeninfo
  (fallback gracieux [] / None si screeninfo absent)
- _enrich_with_monitor_info() ajouté aux payloads dict de capture_dual,
  capture_active_window, et heartbeat_event poussé par main.py
- screeninfo>=0.8 ajouté aux requirements (source + deploy Windows)
- Deploy capturer.py reçoit l'enrichissement de manière additive (pas de
  copie verbatim qui aurait introduit BLUR_SENSITIVE absent côté deploy)

Côté serveur :
- import resolve_target_monitor depuis monitor_router (créé en QW1.1)
- /replay/next : enrichissement action.monitor_resolution avant envoi
  au client (idx, offset_x/y, w, h, source de la décision)
- live_session_manager.add_event : propagation monitor_index +
  monitors_geometry depuis window_capture ET depuis le payload event
  brut (cas heartbeat enrichi sans window/window_title)

Cascade de résolution (cf monitor_router.py) :
1. action.monitor_index (hérité de la session source)
2. session.last_focused_monitor (focus actif vu en dernier heartbeat)
3. composite_fallback (offset 0,0) — backward compat strict

Backward 100% : si geometry vide, fallback composite identique au
comportement actuel mss.monitors[0].

Tests : baseline 89/89 préservée, monitor_router 4/4 OK (total 93/93).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:05:44 +02:00
Dom
fae95c5366 feat(qw1): capture par monitor + propagation offsets dans grounding cascade
_capture_screen() accepte un monitor_idx optionnel (None = composite legacy).
Index logique 0..N-1 mappé sur mss.monitors[idx+1] (mss[0] = composite).

Les 3 niveaux de grounding (OCR, UI-TARS, VLM) propagent l'offset retourné
par la capture pour traduire les coordonnées locales monitor en coordonnées
absolues écran (correct pour pyautogui.click).

find_element_on_screen() accepte monitor_idx et le forwarde aux 3 niveaux.

Backward 100% : monitor_idx=None partout → comportement strictement actuel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 22:55:04 +02:00
Dom
6582a69d31 feat(qw1): MonitorRouter — résolution de l'écran cible pour le replay
Module isolé qui choisit l'écran cible avec stratégie en cascade :
1. action.monitor_index (session source) → cible explicite
2. session.last_focused_monitor → fallback focus actif
3. composite (offset 0,0) → backward compat (comportement actuel)

Backward 100% : actions sans monitor_index → fallback composite identique
au comportement mss.monitors[0] actuel.

Tests : 4 cas (cible OK, fallback focus, fallback composite, index invalide).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 22:50:22 +02:00
Dom
5543e25f9d docs(qw): plan d'implémentation QW suite mai 2026 (~30 tasks bite-sized TDD)
Some checks failed
tests / Lint (ruff + black) (push) Successful in 18s
tests / Tests unitaires (sans GPU) (push) Failing after 17s
tests / Tests sécurité (critique) (push) Has been skipped
Plan d'exécution détaillé pour le sprint QW1+QW2+QW4 :
- Section 0 (preflight) : backup branche+tag Gitea, baseline E2E, smoke démo
- Section 1 (QW1 multi-écrans) : tests + monitor_router + input_handler + Agent V1
- Section 2 (QW2 LoopDetector) : tests + module + hooks api_stream/replay_engine
- Section 3 (QW4 safety_checks) : tests + provider + endpoint + frontend VWB
- Section 4 (docs) : QW_SUITE_MAI.md + maj MEMORY

Chaque task = 4-7 steps de 2-5 min, code complet par step (modules nouveaux),
diffs ciblés (modifs ciblées), commands exactes avec output attendu.

Discipline TDD légère : test rouge → implem → test vert → re-run baseline → commit.

Référence spec : docs/superpowers/specs/2026-05-05-qw-suite-mai-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
backup-pre-qw-suite-mai-2026-05-05
2026-05-05 22:34:13 +02:00