Add a private in-flight helper for replay dispatch, block machine retargeting while an action is still pending on the previous session, and warn on duplicate in-flight entries for the same replay triplet.
Freeze the Notepad runtime dialog success path and add integration coverage for single in-flight dispatch, watchdog late-report documentation, and the known concurrent-poll race as an xfail.
Bug observé sur replay_sess_56c10222 (2026-05-24 20:14) :
action 11 (clic 'Enregistrer' expected_after='Enregistrer sous')
marquée success=True alors que 2 actions plus tard la fenêtre observée
est 'NoMachine Desktop Viewer'. Le polling post-vérif a probablement
matché brièvement 'Enregistrer sous' puis l'écran a changé sans
qu'on ne revérifie.
Dom : "Le contrat est rompu : Léa passe d'une action à l'autre sans
vérifier que la précédente est bonne. Il faut un contrôle de résultat,
si on ne sait pas on demande."
Patch : juste après le match initial, attendre 0.5s et reverifier
la fenêtre active. Si elle a divergé (race condition, dialog auto-
fermée, focus change OS) → matched=False, le flow strict existant
prend le relais avec wrong_window + needs_human.
Ne touche que les cas où expected_after est défini ET pas de
runtime_dialog géré entre temps (le runtime_dialog est légitime de
changer la fenêtre).
Tag rollback : rollback/pre-P0.9-2026-05-24_2148
Léa avait déjà une infra pour les dialogs runtime (`_match_known_runtime_dialog`
+ `_handle_known_runtime_dialog`) mais avec un catalog local limité à
2 entrées. Le DialogResolver R2 côté serveur a 10 entrées centralisées.
P1.MVP : `_try_dialog_resolver_server()` consulte l'endpoint
`/api/v1/dialog/resolve` quand le catalog local n'a pas matché. La
réponse `DialogResolution` est convertie en dialog_spec compatible
avec `_handle_known_runtime_dialog` qui réutilise la cascade existante
(serveur VLM grounding + template matching local).
- Flag `RPA_DIALOG_RESOLVER_AGENT_ENABLED` (OFF par défaut) — rollback runtime
- Auth Bearer via `_auth_headers()` existant
- Timeout 3s, fail-safe sur exception/503/no-match → fallback humain intact
- Zéro régression sur les chemins existants (le catalog local reste 1ère ligne)
Tests unitaires en local (6/6 OK) :
- flag OFF → None
- serveur 503 → None
- matched=False → None
- policy=pause (UAC) → None
- match auto + click_button → dialog_spec valide
- exception réseau → None
Tag rollback : rollback/pre-P1-2026-05-24_2105
Friction UX remontée par Dom sur replay live (replay_sess_63a1313b) :
latence excessive 2-3 minutes après un échec d'action avant que Léa
ne reprenne la main. 120s = trop long pour un humain en supervision.
10s d'inactivité reste le critère prioritaire (déjà en place), donc :
- humain actif : la correction est captée et le replay reprend en ~1s
- humain absent : on libère après 30s au lieu de 120s
5 sites d'appel + signature de fonction (default param) alignés.
Tag rollback : rollback/pre-P0.8-2026-05-24_1912
Référence : message 2026-05-24_1910_claude-to-codex_p07-memory-sanity-fix-human-supervised-bug-frictions-ux.md
Bug observé sur replay_sess_63a1313b 2026-05-24 18:31-18:32 :
_capture_human_correction() côté Léa retourne des human_actions sans
clic humain réel (cause racine côté agent à investiguer = P0.6).
En cascade, memory_record_success était appelé avec coords (0.0, 0.0)
et stockait des entrées poison dans target_memory.db.
Le sanity check existant rejetait < 0 ou > 1 mais laissait passer (0,0)
qui est mathématiquement valide. Au prochain replay, memory_lookup
trouvait l'entrée poison et faisait cliquer Léa au coin haut-gauche.
Patch : rejet explicite de (0,0) + warning au lieu de debug pour les
coords hors [0,1] (besoin de tracabilité runtime).
Filet en aval — la vraie cause côté Léa reste à corriger (P0.6).
Tag rollback : rollback/pre-P0.7-2026-05-24_1850
Symptôme observé sur replay_sess_7a4c8e72 (24/05 17:57) :
- Action act_setup_sess_verify (type=verify_screen) échoue 4x (+3 retries)
- Logs: [VALIDATOR_V2] override success→False verdict=continue conf=0.30
failure_category=None reason='Aucun changement visible pour
verify_screen (normal pour ce type d'action)'
- Replay tombe en status=error à 7/15 (régression vs 12/15 sans V2)
Cause: api_stream.py:3674 testait `if verdict != COMPLETE` (trop large) →
toute action qui ne change pas drastiquement l'écran (verify_screen, wait,
key_combo Ctrl+S avant ouverture dialog, etc.) renvoie verdict=CONTINUE
conf=0.30 du PixelDiffChecker via le default_checker de l'orchestrator,
ce qui était traité comme un échec à overrider.
Fix: override SEULEMENT sur verdict=TERMINATE (échec certain avec
failure_category). CONTINUE = faible signal = on laisse le pipeline
historique trancher.
COMPLETE n'a pas besoin d'être traité ici car on est déjà dans
`if report.success:` (success initial vrai).
Effet:
- verify_screen/wait/key_combo non-interactif → orchestrator retourne
CONTINUE conf=0.30 → V2 ne touche pas report.success (comportement
legacy préservé)
- click qui rate (act_raw_6c1432b3 type cible) → OcrRoiChecker retourne
TERMINATE conf=0.85 failure_category=WRONG_APPLICATION → override OK
Tests R1 inchangés (TERMINATE branch testée explicitement).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>