42 KiB
AUDIT — Contrôles débranchés (serveur)
Date : 2026-05-08
Branche : feature/qw-suite-mai
HEAD : 56e869c46
Périmètre : agent_v0/server_v1/ + core/* importés. Client exclu.
1. Inventaire des fichiers audités
| Fichier | Lignes |
|---|---|
| agent_v0/server_v1/init.py | 0 |
| agent_v0/server_v1/visual_wait.py | 54 |
| agent_v0/server_v1/monitor_router.py | 99 |
| agent_v0/server_v1/replay_failure_logger.py | 143 |
| agent_v0/server_v1/vm_controller.py | 143 |
| agent_v0/server_v1/loop_detector.py | 154 |
| agent_v0/server_v1/worker_stream.py | 172 |
| agent_v0/server_v1/workflow_replay.py | 185 |
| agent_v0/server_v1/safety_checks_provider.py | 195 |
| agent_v0/server_v1/session_worker.py | 253 |
| agent_v0/server_v1/agent_registry.py | 296 |
| agent_v0/server_v1/replay_memory.py | 323 |
| agent_v0/server_v1/execution_plan_runner.py | 373 |
| agent_v0/server_v1/audit_trail.py | 393 |
| agent_v0/server_v1/replay_learner.py | 395 |
| agent_v0/server_v1/run_worker.py | 397 |
| agent_v0/server_v1/live_session_manager.py | 464 |
| agent_v0/server_v1/task_planner.py | 596 |
| agent_v0/server_v1/chat_interface.py | 622 |
| agent_v0/server_v1/replay_verifier.py | 632 |
| agent_v0/server_v1/domain_context.py | 1020 |
| agent_v0/server_v1/replay_engine.py | 1643 |
| agent_v0/server_v1/resolve_engine.py | 2585 |
| agent_v0/server_v1/stream_processor.py | 5137 |
| agent_v0/server_v1/api_stream.py | 5445 |
Total serveur : 21 719 lignes.
Modules core/ effectivement importés par le serveur :
- core.detection.omniparser_adapter (resolve_engine.py:272)
- core.detection.ollama_client, core.detection.vlm_config (resolve_engine.py:502-503, api_stream.py:790)
- core.detection.som_engine (resolve_engine.py:977)
- core.embedding.clip_embedder (resolve_engine.py:1658)
- core.anonymisation (api_stream.py:47)
- core.auth.credential_vault, core.auth.auth_handler (api_stream.py:84-85)
- core.llm.ocr_extractor (api_stream.py:824)
- core.models.workflow_graph (api_stream.py:845)
- core.workflow.shadow_observer, shadow_validator, execution_plan, execution_compiler, ir_builder (api_stream.py:1601-2656)
- core.federation.learning_pack, faiss_global (api_stream.py:4647-4690)
- core.learning.target_memory_store (replay_memory.py:62)
2. Findings par catégorie
2.1 Validations désactivées ou non consommées
F2.1.1 — Pré-check OCR sémantique (_validate_text_at_position) gardé par flag off-by-default
agent_v0/server_v1/api_stream.py:4519-4533- Citation :
_text_precheck_enabled = os.environ.get(
"RPA_ENABLE_TEXT_PRECHECK", "false"
).lower() in ("true", "1", "yes")
if _text_precheck_enabled and result and result.get("resolved"):
_by_text = (request.target_spec.get("by_text") or "").strip()
if _by_text:
from agent_v0.server_v1.resolve_engine import _validate_text_at_position
_is_valid, _observed, _ocr_ms = _validate_text_at_position(
tmp_path,
float(result.get("x_pct", 0) or 0),
float(result.get("y_pct", 0) or 0),
_by_text,
effective_w,
effective_h,
)
- Statut : off-by-default — l'appel à
_validate_text_at_positionne s'exécute QUE siRPA_ENABLE_TEXT_PRECHECK=true. La fonction reste définie enresolve_engine.py:2239-2289mais n'est jamais consommée en production tant que la variable env n'est pas positionnée.
F2.1.2 — _validate_text_at_position retourne True en cas d'échec OCR (politique permissive)
agent_v0/server_v1/resolve_engine.py:2253-2261, 2280, 2287-2289- Citation :
Politique en cas d'échec OCR (lib absente, exception) : retourne
(True, "", 0.0) pour ne pas bloquer le flux. Mieux vaut un faux positif
rare qu'une régression bloquante introduite par la validation elle-même.
"""
reader = _get_validation_ocr_reader()
if reader is None:
return True, "", 0.0
if not expected_text or not expected_text.strip():
return True, "", 0.0
[...]
if x2 - x1 < 10 or y2 - y1 < 10:
return True, "", 0.0
[...]
except Exception as e:
logger.warning("[REPLAY] _validate_text_at_position erreur (%s) — pas de blocage", e)
return True, "", 0.0
- Statut : actif (quand le flag global est on) mais résultat documenté comme intentionnellement permissif sur tout chemin d'erreur. Une erreur OCR = pas de blocage.
F2.1.3 — _pre_check_screen_state (CLIP) bascule match=True sur exception
agent_v0/server_v1/replay_engine.py:1374-1379- Citation :
except Exception as e:
# Ne jamais bloquer le replay en cas d'erreur du pre-check
logger.error(f"Pre-check échoué (non bloquant): {e}")
result["match"] = True # Fallback permissif
result["reason"] = f"precheck_error: {e}"
- Statut : actif mais permissif explicitement (commentaire
# Fallback permissif). Toute exception interne du pre-check CLIP renvoiematch=Trueet l'action passe.
F2.1.4 — Vérification post-action (verify_action/verify_with_critic) skippée pour type/key_combo/wait et popup gérée
agent_v0/server_v1/api_stream.py:3394-3399- Citation :
action_type_for_verify = (original_action or {}).get("type", "unknown")
skip_verify = action_type_for_verify in ("type", "key_combo", "wait")
# Skip aussi la vérification serveur si l'agent a déjà géré la popup
skip_verify = skip_verify or agent_handled_popup
verification = None
if report.success and screenshot_after and not skip_verify:
- Statut : actif. La vérification visuelle post-action ne tourne que pour les click et seulement si
agent_handled_popupest faux.
F2.1.5 — _validate_match_context consommé uniquement dans la branche template strict
agent_v0/server_v1/resolve_engine.py:201, 1864- Le seul appel est
resolve_engine.py:1864à l'intérieur du mode strict, pour le fallback template. Pas appelé dans_resolve_with_precompiled_order(V4) ni dans le mode classique. - Statut : actif sur un seul chemin de la cascade.
2.2 Garde-fous court-circuités (seuils, flags, conditions)
F2.2.1 — Drift > 0.20 ignoré quand template_matching ≥ 0.95 ou hybrid_text_direct ≥ 0.80
agent_v0/server_v1/resolve_engine.py:2367-2390- Citation :
if dx > _RESOLUTION_MAX_DRIFT or dy > _RESOLUTION_MAX_DRIFT:
[...]
_high_confidence_method = (
(method.startswith("template_matching") and score >= 0.95)
or (method == "hybrid_text_direct" and score >= 0.80)
)
if _high_confidence_method:
logger.info(
"[REPLAY] Drift (%.3f, %.3f) > %.2f IGNORÉ : score=%.3f "
"sur %s — résultat visuel fiable, on l'utilise",
dx, dy, _RESOLUTION_MAX_DRIFT, score, method,
)
return result
- Statut : actif. Exemption introduite par
35b27ae49(template ≥ 0.95) puis élargie par40440f1caà hybrid_text_direct ≥ 0.80. La garde de drift est neutralisée pour deux familles de méthodes.
F2.2.2 — Drift check inactif si fallback x/y_pct ressemblent à un placeholder 0.5/0.5 ou 0.0/0.0
agent_v0/server_v1/resolve_engine.py:2359-2363- Citation :
_has_recorded_coords = (
fallback_x_pct > 0.001
and fallback_y_pct > 0.001
and not (abs(fallback_x_pct - 0.5) < 0.001 and abs(fallback_y_pct - 0.5) < 0.001)
)
if _has_recorded_coords:
- Statut : actif. Sans coords enregistrées exploitables, la garde drift est inerte.
F2.2.3 — Self-healing Win+D au retry 1 désactivé (revert)
agent_v0/server_v1/replay_engine.py(commit22c0a2ba6, branchenext_retry == 2conservée seule)- Citation post-revert :
if next_retry == 2:
# Retry 2 : injecter un wait de 2s avant l'action
wait_action = {
"action_id": f"wait_retry_{uuid.uuid4().hex[:6]}",
"type": "wait",
"duration_ms": 2000,
}
actions_to_insert.append(wait_action)
- Statut : retiré. Aucune injection de gesture de récupération avant le retry 1 — boucle directe sur la même action.
F2.2.4 — Pre-check skip si heartbeat > 10s ou timeout > 500ms
agent_v0/server_v1/api_stream.py:999-1001, 3100-3130- Citation :
_HEARTBEAT_MAX_AGE_SECONDS = 10.0
_PRECHECK_SIMILARITY_THRESHOLD = 0.85
[...]
if age <= _HEARTBEAT_MAX_AGE_SECONDS:
[...]
precheck_result = await asyncio.wait_for(
loop.run_in_executor(...),
timeout=0.5, # Max 500ms pour le pre-check
)
except asyncio.TimeoutError:
logger.warning(...)
precheck_result = None
- Statut : actif. Le pre-check CLIP est skip silencieusement si heartbeat trop ancien ou si l'embed prend > 500ms.
F2.2.5 — VLM Quick Find : confidence < 0.3 → ignoré (résultat valide perdu sous le seuil)
agent_v0/server_v1/resolve_engine.py:655-662- Citation :
if x_pct is None or y_pct is None or confidence < 0.3:
logger.info(
"VLM Quick Find : élément non trouvé ou confiance trop basse "
"(%.1fs, confidence=%.2f) pour '%s'",
- Statut : actif. Tout retour VLM avec confidence < 0.3 est dropé.
F2.2.6 — Image client tronquée → remplacement silencieux par dernier heartbeat
agent_v0/server_v1/api_stream.py:4422- Citation :
if img.height < 800 or img.width < 1200:
logger.warning(
"[RESOLVE_TARGET] Image client tronquée %dx%d (declared %dx%d) — "
"fallback heartbeat full screen",
- Statut : actif. Toute image reçue < 1200x800 est remplacée par un screenshot heartbeat (mémoire ou disque) avant cascade. Seuil élargi par
7233df2bb(était 400x200 avant).
F2.2.7 — CLIP mismatch < 0.75 retourne resolved=False mais ne bloque qu'en mode strict avec embedding fourni
agent_v0/server_v1/resolve_engine.py:1655-1691- Citation :
clip_embedding = target_spec.get("clip_embedding")
if clip_embedding:
[...]
if clip_sim < 0.75:
logger.warning(
f"CLIP MISMATCH : sim={clip_sim:.3f} < 0.75 — "
f"écran actuel trop différent de l'enregistrement"
)
return {
"resolved": False,
"method": "clip_mismatch",
- Statut : actif uniquement si
clip_embeddingfourni ET mode strict. Pour les workflows qui n'embarquent pas l'embedding, ce filet est inerte.
2.3 Flags d'environnement avec défaut permissif
F2.3.1 — RPA_ENABLE_TEXT_PRECHECK, défaut "false"
agent_v0/server_v1/api_stream.py:4519-4521- Citation :
_text_precheck_enabled = os.environ.get(
"RPA_ENABLE_TEXT_PRECHECK", "false"
).lower() in ("true", "1", "yes")
- Statut : off par défaut. Sans surcharge en environnement, le pré-check OCR ne s'exécute jamais.
F2.3.2 — RPA_AUTH_DISABLED, défaut absent (auth obligatoire) mais permet de tout débrayer
agent_v0/server_v1/api_stream.py:107-119- Citation :
_AUTH_DISABLED = os.environ.get("RPA_AUTH_DISABLED", "").lower() in (
"1", "true", "yes",
)
[...]
if _AUTH_DISABLED:
logger.warning(
"[SÉCURITÉ] RPA_AUTH_DISABLED=true — authentification Bearer DÉSACTIVÉE. ..."
)
API_TOKEN = _API_TOKEN_ENV or secrets.token_hex(32)
- Statut : par défaut auth obligatoire, mais flag explicite documenté pour la débrayer.
F2.3.3 — RPA_LOOP_DETECTOR_ENABLED, défaut "1" (activé)
agent_v0/server_v1/loop_detector.py:42-47, 78-79- Citation :
def _env_bool_enabled(name: str) -> bool:
val = os.environ.get(name, "1").strip().lower()
return val not in ("0", "false", "no", "off", "")
[...]
if not _env_bool_enabled("RPA_LOOP_DETECTOR_ENABLED"):
return LoopVerdict(detected=False)
- Statut : on par défaut, désactivable via
RPA_LOOP_DETECTOR_ENABLED=0.
F2.3.4 — RPA_SAFETY_CHECKS_LLM_ENABLED, défaut "1" (activé)
agent_v0/server_v1/safety_checks_provider.py:42-44, 70- Citation :
if safety_level == "medical_critical" and _env_bool_enabled("RPA_SAFETY_CHECKS_LLM_ENABLED"):
- Statut : on par défaut, mais ne tourne que si
safety_level == "medical_critical"dans l'action.
F2.3.5 — RPA_PII_BLUR_SERVER, défaut "true" (activé)
agent_v0/server_v1/api_stream.py:1023- Citation :
_PII_BLUR_ENABLED = os.environ.get("RPA_PII_BLUR_SERVER", "true").lower() in ("true", "1", "yes")
- Statut : on par défaut.
2.4 Étapes de cascade neutralisées
F2.4.1 — _resolve_by_yolo défini, importé, jamais appelé
- Définition :
agent_v0/server_v1/resolve_engine.py:293 - Import :
agent_v0/server_v1/api_stream.py:4363 - Recherche
_resolve_by_yolo(dans le serveur entier : 0 site d'appel. - Statut : fonction morte. La détection OmniParser/YOLO n'est plus dans la cascade exécutée.
F2.4.2 — _resolve_with_precompiled_order (V4) appelé seulement si target_spec["resolve_order"] présent
agent_v0/server_v1/resolve_engine.py:1613-1635- Citation :
resolve_order = target_spec.get("resolve_order")
if resolve_order and isinstance(resolve_order, list):
[...]
result = _resolve_with_precompiled_order(...)
if result and result.get("resolved"):
return result
[...]
logger.info(
"V4 resolve : toutes les méthodes pré-compilées ont échoué, "
"fallback cascade legacy"
)
- Statut : actif quand un plan V4 est compilé ; sinon inerte. Fallback cascade legacy systématique en cas d'échec.
F2.4.3 — Étape grounding VLM directe conditionnée à by_text_source ∈ {ocr, vlm} ET has_window
agent_v0/server_v1/resolve_engine.py:1696-1715- Citation :
by_text_source = target_spec.get("by_text_source", "")
has_window = bool(target_spec.get("window_capture", {}).get("rect"))
if by_text_strict and by_text_source in ("ocr", "vlm") and has_window:
grounding_result = _resolve_by_grounding(...)
- Statut : actif sur ces deux conditions seulement. Si
by_text_sourceest vide ou autre, ou siwindow_capture.rectabsent, le grounding direct est sauté.
F2.4.4 — _resolve_by_ocr_text (hybrid_text_direct) reconnecté le 2026-05-06 dans la cascade strict (commit 1cbec2806)
agent_v0/server_v1/resolve_engine.py:1750-1790- Citation du commit :
fix(resolve): rebrancher hybrid_text_direct dans _resolve_target_sync
[...] la fonction _resolve_by_ocr_text (resolve_engine.py:1447) existait
déjà mais [...] n'était appelée QUE depuis le runtime V4 [...]
- Statut : actif depuis
1cbec2806. Avant : étape OCR direct n'était pas dans la cascade strict pour les workflows non-V4.
F2.4.5 — Template matching mode strict : seuil 0.90 (étape 2 fallback)
agent_v0/server_v1/resolve_engine.py:1733, 1847-1875- Citation :
result = _resolve_by_template_matching(
[...]
confidence_threshold=0.90,
)
if result:
score = result.get("score", 0)
# Score >= 0.95 : match quasi-parfait, pas besoin de valider le contexte
if score >= 0.95:
[...]
return result
elif _validate_match_context(result, fallback_x_pct, fallback_y_pct, target_spec):
[...]
- Statut : actif.
_validate_match_contextskippé si score ≥ 0.95.
2.5 Fonctions améliorantes définies mais non appelées
F2.5.1 — _resolve_by_yolo (résolution OmniParser+template, défini resolve_engine.py:293, jamais appelé)
- Voir F2.4.1.
F2.5.2 — _fuzzy_match importé dans api_stream.py mais jamais appelé
- Définition :
agent_v0/server_v1/resolve_engine.py:2086 - Import :
agent_v0/server_v1/api_stream.py:4372 - Recherche
_fuzzy_match(dans api_stream.py : 0 appel hors la ligne d'import. - Statut : import mort. Le fuzzy match utilisé en runtime est
_text_match_fuzzy(resolve_engine.py:2213), distinct.
F2.5.3 — _get_omniparser, _build_target_description importés dans api_stream.py mais non appelés directement
agent_v0/server_v1/api_stream.py:4362, 4365- Statut : imports utilisés indirectement via
_resolve_target_syncqui les appelle en interne. Pas un finding bloquant — pas de fonction améliorante hors usage interne.
2.6 Marqueurs de dette (TODO/FIXME/disabled/démo) dans le serveur
F2.6.1 — TODO task_planner.py:400
- Citation :
# Boucle : TODO — lister les éléments puis itérer - Statut : commentaire de dette dans
task_planner.py.
F2.6.2 — Commentaire « 8 mai 2026 : désactivé par défaut pour la démo GHT »
agent_v0/server_v1/api_stream.py:4512- Citation :
# 8 mai 2026 : désactivé par défaut pour la démo GHT. Calibrage du
# radius_px et min_token_ratio à finaliser post-démo (cf. rapport
# docs/E2E_TEST_RUN_2026-05-08.md). Le pré-check était trop strict
# sur les onglets à 2 tokens (Examens cliniques, Synthèse Urgences)
# → faux rejets → cascade locale Léa V1 → clic au pif. Réactivable
# via env RPA_ENABLE_TEXT_PRECHECK=true. Le code et les tests
# restent en place pour reprise post-démo.
- Statut : marqueur démo explicite.
F2.6.3 — Mention « Fallback permissif » dans _pre_check_screen_state
agent_v0/server_v1/replay_engine.py:1377—result["match"] = True # Fallback permissif
F2.6.4 — Ré-introduction explicite « non-bloquant » dans _validate_text_at_position
agent_v0/server_v1/resolve_engine.py:2288—logger.warning("[REPLAY] _validate_text_at_position erreur (%s) — pas de blocage", e)
F2.6.5 — Mode autonome → pause_for_human ignorée silencieusement
agent_v0/server_v1/api_stream.py:3011-3017- Citation :
# Mode autonome sans safety_checks → skip (comportement legacy)
logger.info(
"pause_for_human ignorée (mode autonome) — replay %s continue",
owning_replay["replay_id"] if owning_replay else "?"
)
queue.pop(0)
_replay_queues[session_id] = queue
continue
- Statut : actif. La supervision n'est utilisée que si
execution_mode != "autonomous"ou sisafety_level/safety_checksdéclarés. Ce câblageexecution_mode → superviseda été corrigé par7233df2bb.
F2.6.6 — Commentaire # Fallback permissif/pas de blocage cumulés
- Présents dans 3 fonctions de validation :
_pre_check_screen_state(replay_engine.py:1377),_validate_text_at_position(resolve_engine.py:2288), et politique de_get_validation_ocr_reader(resolve_engine.py:2196).
3. Commits récents qui ont désactivé des contrôles
(Sur les 20 derniers commits du dossier agent_v0/server_v1/)
56e869c46(8 mai) —fix(replay): bug TypeError log + flag pré-check OCR off par défaut (démo GHT): ajouteRPA_ENABLE_TEXT_PRECHECK(default"false") qui débraye intégralement l'appel à_validate_text_at_position.40440f1ca(7 mai) —fix(replay): cure régression b584bbabc — fallback recorded_coords aveugle: restaureresolved=Falsesur drift trop grand (annule le fallback aveugle introduit parb584bbabc) ; étend l'exemption drift àhybrid_text_direct ≥ 0.80(resolve_engine.py:2380-2390).7233df2bb(7 mai) —fix(replay): câblage execution_mode supervised + seuil large fallback heartbeat: élargit le seuil de détection image tronquée à< 1200×800(était< 400×200) → fallback heartbeat plus fréquent ; forceexecution_mode='supervised'par défaut quand non précisé.f62fda575(7 mai) —fix(stream): /resolve_target — fallback heartbeat full si image client tronquée: introduit le remplacement silencieux de l'image client par un heartbeat disque/mémoire si tronquée.22c0a2ba6(6 mai) —revert: désactiver self-healing Win+D auto (cercle vicieux): retire l'injection automatique de Win+D au retry 1 surverification_failed/no_screen_change.c969f93a2(6 mai) —fix(replay): self-healing Win+D auto au retry 1: commit de la fonctionnalité, reverté par22c0a2ba6.1cbec2806(6 mai) —fix(resolve): rebrancher hybrid_text_direct dans _resolve_target_sync: reconnecte_resolve_by_ocr_textdans la cascade strict, qui auparavant n'était appelé que par le chemin V4 pré-compilé (non actif dans la majorité des workflows).b584bbabc(1 mai) —fix(stream): robustesse proxy VWB→streaming + ciblage textuel pour démo UHCD: avait remplacé le rejet strict du drift par unfallback_recorded_coords(resolved=True). Reverté factuellement par40440f1cale 7 mai.35b27ae49(2 mai) —fix(stream+vwb): chaîne replay robuste — auth, anchor type_text, lock async, drift, prompt LLM: introduit l'exemption drift pourtemplate_matching ≥ 0.95(point d'entrée du finding F2.2.1).
4. Pistes prioritaires (P0 « pré-check OCR rejette systématiquement »)
Findings directement reliés au P0 (motif factuel : « pré-check OCR rejette systématiquement, contrôles débranchés suite à checkout antérieur ») :
-
F2.3.1 + F2.6.2 —
agent_v0/server_v1/api_stream.py:4519-4521: flagRPA_ENABLE_TEXT_PRECHECKà défaut"false". C'est le geste d'extinction explicite mentionné dans le commit56e869c46; le pré-check OCR ne peut rejeter quoi que ce soit en l'état tant que la variable d'environnement n'est pas positionnée à"true"côté service rpa-streaming. Confirme directement la piste signalée. -
F2.1.1 —
agent_v0/server_v1/resolve_engine.py:2239-2289(_validate_text_at_position) +agent_v0/server_v1/api_stream.py:4525-4533(point d'appel) : la fonction est conservée en place mais son point d'appel est conditionné par F2.3.1. Tant que le flag est off, la fonction est définie mais non consommée — état exact « contrôle débranché ». -
F2.6.5 —
agent_v0/server_v1/api_stream.py:3011-3017:pause_for_human ignorée (mode autonome) — replay continue. Siexecution_moden'est pas propagé jusqu'au replay (cf. commit7233df2bbqui corrige ce câblage), la pause supervisée censée intercepter un rejet OCR est skipée et la file d'actions continue. Combiné à F2.3.1, donne le motif observé : « rejets pré-check silencieux → cascade locale Léa → clic au pif » (extrait commit message56e869c46).
5. Findings côté CLIENT (agent_v0/agent_v1/core/executor.py)
Fichier audité : agent_v0/agent_v1/core/executor.py (2893 lignes). Aucune occurrence de RPA_ENABLE_*, RPA_DISABLE_*, if False: ou if 0: dans ce fichier.
5.1 Validations désactivées ou non consommées
Aucun finding (le client ne contient aucun bloc de validation neutralisé : la pré-vérif titre fenêtre, _check_and_pause_on_system_dialog et la cascade Observer→Policy sont toutes appelées en flux nominal).
5.2 Garde-fous court-circuités (seuils, flags, conditions)
F5.2.1 — _check_and_pause_on_system_dialog fail-closed sur exception (durcissement, pas une désactivation)
agent_v0/agent_v1/core/executor.py:2001-2043- Citation :
except Exception as e:
# Fix P0-D : fail-closed (principe "faux positif tolérable,
# faux négatif catastrophique"). [...]
self._system_dialog_pause = {
"category": "unknown_check_failed",
[...]
}
[...]
return True
- Statut : actif. C'est un fail-closed (toute erreur de détection → pause supervisée), pas un fallback permissif. Listé pour traçabilité.
F5.2.2 — Seuil template-matching _find_text_on_screen durci à 0.75
agent_v0/agent_v1/core/executor.py:2367- Citation :
threshold = 0.75 # Démo GHT 8 mai — éviter faux positifs (placeholders italiques, tabs voisins). En dessous, mieux vaut tomber en mode apprentissage humain qu'un clic au pif.
- Statut : actif. Seuil élevé en démo GHT (commit
7847a0e82, 7 mai) — under threshold = pas de match retourné. Pas une désactivation, un durcissement.
F5.2.3 — Skip conditional_on_window : action acquittée success=True quand le dialogue n'est pas apparu
agent_v0/agent_v1/core/executor.py:567-592- Citation :
if not match:
[...]
print(
f" [SKIP] Dialogue '{cond_window}' absent → action skippée"
)
result["success"] = True
result["warning"] = "conditional_skipped"
return result
- Statut : actif. Comportement attendu (skip explicite avec
warning=conditional_skipped) mais l'action est rapportéesuccess=Trueau serveur — pas une erreur côté replay engine.
F5.2.4 — wrong_window_skipped : action skippée silencieusement après timeout apprentissage
agent_v0/agent_v1/core/executor.py:754-764- Citation :
else:
# Timeout ou pas d'action → skipper cette action
# L'état est peut-être déjà correct (ex: Ctrl+S
# a sauvé sans dialogue → action de dialogue inutile)
result["success"] = True
result["warning"] = "wrong_window_skipped"
logger.info(
f"[LEA] Wrong window sans correction → skip "
f"(l'état est peut-être déjà atteint)"
)
- Statut : actif. Si l'humain ne corrige pas dans les 120s (
_capture_human_correction(timeout_s=120)), l'action est marquée success=True. Mêmes lignes pourpolicy_skip(executor.py:993-996).
F5.2.5 — Polling timeout REPLAY étendu à 30s pour démo GHT
agent_v0/agent_v1/core/executor.py:1786-1794- Citation :
# 8 mai 2026 — démo GHT : 5s → 30s. Le serveur peut exécuter
# extract_text (5-7s) PUIS dispatcher l'action suivante dans
# la même réponse HTTP. À 5s, le client coupait avant la
# réponse [...]
timeout=30,
- Statut : actif. Marqueur démo, modification non commitée à HEAD (status
Not Committed Yet 2026-05-08). Pas un contrôle débranché, contournement coûts d'IO.
5.3 Flags d'environnement avec défaut permissif
F5.3.1 — RPA_OLLAMA_HOST, défaut "localhost"
agent_v0/agent_v1/core/executor.py:2224(et autres sites)- Citation :
ollama_host = os.environ.get("RPA_OLLAMA_HOST", "localhost")
- Statut : configuration uniquement, pas un garde-fou.
Aucun autre flag environnemental dans le client : pas de RPA_ENABLE_*/RPA_DISABLE_* côté agent V1.
5.4 Étapes de cascade neutralisées
F5.4.1 — Self-healing désactivé côté client (revert miroir du serveur)
- Le client n'embarque pas de logique self-healing autonome — l'injection Win+D était purement serveur (cf. F2.2.3, commit
22c0a2ba6). Côté client, la branche d'apprentissage humain (_capture_human_correction) reste l'unique recours en cas d'échec retry. - Statut : pas un finding spécifique au client.
5.5 Fonctions améliorantes définies mais non appelées
F5.5.1 — _handle_possible_popup (legacy clavier Enter/Escape/Tab+Enter) toujours définie
agent_v0/agent_v1/core/executor.py:2430-2472- Citation :
def _handle_possible_popup(self) -> bool:
"""Tenter de gerer une popup imprevue.
[...]
Strategie simple (non bloquante, max ~3s) :
1. Essayer Enter (valide le bouton par defaut de la popup)
2. Si ca ne marche pas, essayer Escape (ferme la popup)
3. Si ca ne marche pas, essayer Tab + Enter [...]
- Recherche
_handle_possible_popup(dans le client : 1 site (la définition). 0 site d'appel hors la définition. - Statut : fonction morte côté client. Le chemin actif est
_handle_popup_vlm(executor.py:2102) + Observer/Policy. La version "clavier seul" est conservée mais non câblée.
5.6 Marqueurs de dette (TODO/FIXME/disabled/démo) dans le client
F5.6.1 — Marqueur explicite « démo GHT » multiple
agent_v0/agent_v1/core/executor.py:1786, 1813, 1835, 2367: 4 commentaires « 8 mai 2026 — démo GHT » documentant des changements ciblés (timeout polling, plan B pause UX, threshold FIND-TEXT 0.75).
F5.6.2 — Plusieurs except Exception: pass silencieux
agent_v0/agent_v1/core/executor.py:455-456, 722-723, 958-959, 1017-1018, 1127-1128, 1244-1245, 1286-1287, 2619-2620- Statut : 8 sites, tous sur des chemins best-effort (notification, snapshot UIA, log d'apprentissage). Aucun ne masque une décision de sécurité.
6. Autres fichiers .py modifiés < 14 jours (hors serveur/client déjà audités)
Périmètre : 23 fichiers Python modifiés depuis 2026-04-24 hors tests/, docs/, visual_workflow_builder/, web_dashboard/, agent_chat/, _archive/, tools/ et le dossier agent_v0/server_v1/ (déjà audité §1-§4) et agent_v0/agent_v1/core/executor.py (déjà audité §5).
6.1 core/execution/observe_reason_act.py (2008 lignes)
F6.1.1 — Bloc if False: désactivant le pre-check VLM par-clic
core/execution/observe_reason_act.py:1704-1713- Citation :
# --- Pas de pre-check VLM (le pipeline FAST→SMART→THINK a déjà validé) ---
if False:
try:
pre_check = self._verify_pre_click(x, y, target_text, target_desc)
if not pre_check:
print(f"⛔ [ORA/pre-check] L'élément à ({x}, {y}) ne correspond PAS à '{target_text}' — abandon du clic")
return False
except Exception as e:
print(f"⚠️ [ORA/pre-check] Erreur vérification: {e}")
- Statut : désactivation explicite par
if False:. Le commentaire justifie : « le pipeline FAST→SMART→THINK a déjà validé ».
F6.1.2 — _verify_pre_click : return True permissif sur erreur HTTP/exception
core/execution/observe_reason_act.py:1917, 1921- Citation :
return True # En cas d'erreur HTTP, on laisse passer
[...]
return True # En cas d'erreur, on laisse passer
- Statut : fonction conservée mais devenue inerte (cf. F6.1.1). Si réactivée, retourne
True(passe le clic) sur toute erreur Ollama/réseau.
F6.1.3 — _act_type : texte vide → return True
core/execution/observe_reason_act.py:1740-1742- Citation :
if not decision.value:
logger.warning("🎯 [ORA/type] Pas de texte à saisir")
return True # Vide = rien à faire, pas un échec
- Statut : actif. Comportement documenté.
F6.1.4 — Cascade post-shortcut : timeout retourne True après ≥1 dialog géré
core/execution/observe_reason_act.py:1547-1550- Citation :
if _elapsed() >= total_timeout:
print(f"⏳ [ORA/post-shortcut] Timeout cascade ({total_timeout:.0f}s, "
f"{dialogs_handled} dialog(s) géré(s))")
return True # au moins un dialog traité → considéré OK
- Statut : actif. Politique permissive sur timeout cascade dialogues.
F6.1.5 — Flag RPA_USE_FAST_PIPELINE, défaut "1" (activé)
core/execution/observe_reason_act.py:1634- Citation :
_use_fast = os.environ.get('RPA_USE_FAST_PIPELINE', '1') == '1'
- Statut : on par défaut. Désactivable via env.
6.2 core/grounding/fast_pipeline.py (216 lignes)
F6.2.1 — Expression mort-née if False else screenshot_pil dans appel arbiter
core/grounding/fast_pipeline.py:163- Citation :
screenshot_pil=screenshot_pil or snapshot.elements[0] if False else screenshot_pil,
- Statut : à cause du
if False, l'expression est équivalente àscreenshot_pil=screenshot_pil. La branchesnapshot.elements[0]n'est jamais évaluée. Probable reliquat d'expérimentation.
6.3 core/grounding/title_verifier.py (174 lignes)
F6.3.1 — has_title_changed retourne True si un seul titre est vide
core/grounding/title_verifier.py:73-74- Citation :
if not title_before or not title_after:
return True # Un des deux est vide = changement
- Statut : actif. Politique documentée —
not bloquante(échec lecture titre = signal de changement).
6.4 core/grounding/ui_tars_grounder.py (288 lignes)
F6.4.1 — available toujours True sans vérifier le worker
core/grounding/ui_tars_grounder.py:135-137- Citation :
@property
def available(self) -> bool:
return True # Toujours disponible — le script se lance à la demande
- Statut : actif. Pas de probe socket — la disponibilité est assumée et l'erreur réelle remonte au moment de
ground().
6.5 Fichiers sans finding
Audit pattern par pattern (if False, return True/False suspects, RPA_ENABLE_/RPA_DISABLE_, # disabled, # bypass, # TODO re-enable, marqueurs démo, blocs try swallow exception sur fonction de validation) :
| Fichier | Findings |
|---|---|
agent_v0/agent_v1/main.py |
aucun finding |
agent_v0/agent_v1/network/feedback_bus.py |
aucun finding |
agent_v0/agent_v1/ui/chat_window.py |
aucun finding (le marqueur # démo GHT ligne 846 documente uniquement un comportement UX) |
agent_v0/agent_v1/ui/notifications.py |
aucun finding (idem ligne 143) |
agent_v0/agent_v1/ui/paused_toast.py |
aucun finding |
agent_v0/agent_v1/vision/capturer.py |
aucun finding |
core/execution/input_handler.py |
aucun finding |
core/grounding/dialog_handler.py |
aucun finding |
core/grounding/element_signature.py |
aucun finding |
core/grounding/fast_detector.py |
aucun finding |
core/grounding/infigui_worker.py |
aucun finding |
core/grounding/pipeline.py |
aucun finding |
core/grounding/server.py |
aucun finding |
core/grounding/shadow_learning_hook.py |
aucun finding |
core/grounding/smart_matcher.py |
aucun finding |
core/grounding/template_matcher.py |
aucun finding |
core/grounding/think_arbiter.py |
aucun finding (available = True ligne 38 même pattern que F6.4.1, mais arbiter délègue au grounder qui détient F6.4.1) |
core/knowledge/ui_patterns.py |
aucun finding |
core/llm/ocr_extractor.py |
aucun finding |
core/llm/t2a_decision.py |
aucun finding |
7. Datation git des findings (toutes sections confondues)
| Finding | Fichier:ligne | Commit | Date | Message commit (raccourci) |
|---|---|---|---|---|
| F2.1.1 | api_stream.py:4519-4533 | 56e869c46 (gate) + 40440f1ca (corps) |
2026-05-08 / 2026-05-07 | flag pré-check OCR off / cure régression b584bbabc |
| F2.1.2 | resolve_engine.py:2253-2289 | 40440f1ca |
2026-05-07 | cure régression b584bbabc |
| F2.1.3 | replay_engine.py:1374-1379 | 4509038bf |
2026-04-09 | refactor api_stream.py 6400→3350 |
| F2.1.4 | api_stream.py:3394-3399 | d5deac302 (corps) + ae65be255 (3398) |
2026-03-26 / 2026-03-18 | feat replay visuel VLM-first |
| F2.1.5 | resolve_engine.py:201, 1864 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.2.1 | resolve_engine.py:2367-2390 | 35b27ae49 (intro) → 40440f1ca (élargi) |
2026-05-02 / 2026-05-07 | chaîne replay robuste / cure régression |
| F2.2.2 | resolve_engine.py:2359-2363 | a21f1ea9f |
2026-04-11 | garde qualité résolution |
| F2.2.3 | replay_engine.py (revert) | c969f93a2 (intro) → 22c0a2ba6 (revert) |
2026-05-06 | self-healing Win+D / désactivation cercle vicieux |
| F2.2.4 | api_stream.py:999-1001 | d5deac302 |
2026-03-26 | feat replay visuel VLM-first |
| F2.2.5 | resolve_engine.py:655-662 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.2.6 | api_stream.py:4422 | 7233df2bb |
2026-05-07 | câblage execution_mode + seuil heartbeat élargi |
| F2.2.7 | resolve_engine.py:1655-1691 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.3.1 | api_stream.py:4519-4521 | 56e869c46 |
2026-05-08 | flag pré-check OCR off par défaut (démo GHT) |
| F2.3.2 | api_stream.py:107-119 | 93ef93e56 |
2026-04-14 | API streaming fail-closed |
| F2.3.3 | loop_detector.py:42-47 | 2a51a844b |
2026-05-05 | LoopDetector composite |
| F2.3.4 | safety_checks_provider.py:42-44 | 7c6945171 |
2026-05-05 | SafetyChecksProvider hybride |
| F2.3.5 | api_stream.py:1023 | 93ef93e56 |
2026-04-14 | API streaming fail-closed |
| F2.4.1 | resolve_engine.py:293 (def) | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.4.2 | resolve_engine.py:1613-1635 | f6ad5ff2b |
2026-04-10 | runtime V4 honore resolve_order |
| F2.4.3 | resolve_engine.py:1696-1715 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.4.4 | resolve_engine.py:1750-1790 | 1cbec2806 |
2026-05-06 | rebrancher hybrid_text_direct |
| F2.4.5 | resolve_engine.py:1733, 1847-1875 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.5.1 | resolve_engine.py:293 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.5.2 | api_stream.py:4372 (import mort) | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.5.3 | api_stream.py:4362, 4365 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.6.1 | task_planner.py:400 | 99041f011 |
2026-04-09 | pipeline complet MACRO/MÉSO/MICRO |
| F2.6.2 | api_stream.py:4512 | 56e869c46 |
2026-05-08 | flag pré-check OCR off par défaut |
| F2.6.3 | replay_engine.py:1377 | 4509038bf |
2026-04-09 | refactor api_stream.py |
| F2.6.4 | resolve_engine.py:2288 | 40440f1ca |
2026-05-07 | cure régression b584bbabc |
| F2.6.5 | api_stream.py:3011-3017 | 964856ab3 (intro) → 35b27ae49 / 65da55731 (raffinements) |
2026-04-29 / 2026-05-02 / 2026-05-05 | extract_text serveur / chaîne replay robuste / SafetyChecksProvider |
| F2.6.6 | replay_engine.py:1377 + resolve_engine.py:2196, 2288 | 4509038bf + 40440f1ca |
2026-04-09 / 2026-05-07 | refactor / cure régression |
| F5.2.1 | executor.py:2001-2043 | (déjà fail-closed antérieur) | — | durcissement, pas désactivation |
| F5.2.2 | executor.py:2367 | 7847a0e82 |
2026-05-07 | toast paused supervisée + threshold FIND-TEXT 0.75 |
| F5.2.3 | executor.py:567-592 | (antérieur) | — | (chemin conditionnel intégré) |
| F5.2.4 | executor.py:754-764 | (antérieur) | — | mode apprentissage humain |
| F5.2.5 | executor.py:1786-1794 | non commité (workdir) | 2026-05-08 | démo GHT (uncommitted change) |
| F5.5.1 | executor.py:2430-2472 | (antérieur) | — | legacy popup handler |
| F5.6.1 | executor.py:1786, 1813, 1835, 2367 | 7847a0e82 + workdir |
2026-05-07 / 2026-05-08 | démo GHT |
| F6.1.1 | observe_reason_act.py:1705 | e2046837c |
2026-04-25 | Phase 5 — pipeline FAST→SMART→THINK dans ORA |
| F6.1.2 | observe_reason_act.py:1917, 1921 | 8903f3543 |
2026-04-22 | feat ORA — vérification pré-action VLM |
| F6.1.3 | observe_reason_act.py:1742 | 0c5fffe95 |
2026-04-22 | boucle ORA observe→raisonne→agit |
| F6.1.4 | observe_reason_act.py:1550 | 487bcb861 |
2026-04-26 | cascade post-raccourci DialogHandler/OCR |
| F6.1.5 | observe_reason_act.py:1634 | e2046837c |
2026-04-25 | Phase 5 — FAST→SMART→THINK dans ORA |
| F6.2.1 | fast_pipeline.py:163 | b30d4b665 |
2026-04-25 | Phase 4 — Pipeline orchestré FAST→SMART→THINK |
| F6.3.1 | title_verifier.py:73-74 | 343d6fbe9 |
2026-04-26 | EasyOCR remplace docTR (FastDetector + TitleVerifier) |
| F6.4.1 | ui_tars_grounder.py:137 | 487bcb861 |
2026-04-26 | cascade post-raccourci DialogHandler/OCR |
8. Code original avant désactivation (quand récupérable)
F2.1.1 / F2.3.1 / F2.6.2 — Pré-check OCR (api_stream.py:4519-4533)
Avant (commit 40440f1ca, 2026-05-07) — pré-check appelé inconditionnellement après résolution :
if result and result.get("resolved"):
_by_text = (request.target_spec.get("by_text") or "").strip()
if _by_text:
from agent_v0.server_v1.resolve_engine import _validate_text_at_position
_is_valid, _observed, _ocr_ms = _validate_text_at_position(
tmp_path,
float(result.get("x_pct", 0) or 0),
float(result.get("y_pct", 0) or 0),
_by_text,
effective_w,
effective_h,
)
Après (HEAD 56e869c46, 2026-05-08) — pré-check gardé par flag off-by-default :
_text_precheck_enabled = os.environ.get(
"RPA_ENABLE_TEXT_PRECHECK", "false"
).lower() in ("true", "1", "yes")
if _text_precheck_enabled and result and result.get("resolved"):
_by_text = (request.target_spec.get("by_text") or "").strip()
if _by_text:
from agent_v0.server_v1.resolve_engine import _validate_text_at_position
_is_valid, _observed, _ocr_ms = _validate_text_at_position(...)
F2.2.3 — Self-healing Win+D au retry 1 (revert)
Avant (commit c969f93a2, 2026-05-06) — code introduit (non récupéré ici via git show car branche restaurée par revert immédiat).
Après (HEAD via 22c0a2ba6, 2026-05-06) — branche next_retry == 1 retirée, seule next_retry == 2 (wait 2s) conservée :
if next_retry == 2:
wait_action = {
"action_id": f"wait_retry_{uuid.uuid4().hex[:6]}",
"type": "wait",
"duration_ms": 2000,
}
actions_to_insert.append(wait_action)
F2.2.6 — Seuil image tronquée
Avant (f62fda575, 2026-05-07) — seuil minimal < 400×200 (placeholders triviaux) :
if img.height < 200 or img.width < 400:
[...]
Après (7233df2bb, 2026-05-07) — seuil élargi à < 1200×800 :
if img.height < 800 or img.width < 1200:
[...]
F2.4.4 — Reconnect hybrid_text_direct dans cascade strict
Avant (avant 1cbec2806, ≤ 2026-05-05) — _resolve_by_ocr_text n'était appelée QUE depuis le runtime V4 pré-compilé (extrait commit message). Code original non re-extrait ligne par ligne (commit message factuel suffit).
Après (HEAD via 1cbec2806, 2026-05-06) — appel ajouté dans _resolve_target_sync (cascade strict, resolve_engine.py:1750-1790).
F6.1.1 — Désactivation pre-check VLM par-clic (observe_reason_act.py:1704-1713)
Avant (commit 8903f3543, 2026-04-22) :
# --- Vérification pré-action (skip si UI-TARS a déjà validé visuellement) ---
if target_text and method_used not in ('template', 'ui_tars') and MSS_AVAILABLE and PIL_AVAILABLE:
try:
pre_check = self._verify_pre_click(x, y, target_text, target_desc)
if not pre_check:
print(f"⛔ [ORA/pre-check] L'élément à ({x}, {y}) ne correspond PAS à '{target_text}' — abandon du clic")
return False
except Exception as e:
print(f"⚠️ [ORA/pre-check] Erreur vérification: {e}")
Après (HEAD via e2046837c, 2026-04-25) :
# --- Pas de pre-check VLM (le pipeline FAST→SMART→THINK a déjà validé) ---
if False:
try:
pre_check = self._verify_pre_click(x, y, target_text, target_desc)
if not pre_check:
print(f"⛔ [ORA/pre-check] L'élément à ({x}, {y}) ne correspond PAS à '{target_text}' — abandon du clic")
return False
except Exception as e:
print(f"⚠️ [ORA/pre-check] Erreur vérification: {e}")
Findings sans version « avant » récupérable (≤ 14 jours)
Les findings suivants n'ont pas de version « activée » dans la fenêtre 14 jours (la désactivation est antérieure ou native dès l'introduction du code) :
- F2.1.3, F2.1.4, F2.1.5 (commits
4509038bfdu 2026-04-09 etd5deac302du 2026-03-26) - F2.2.2, F2.2.4, F2.2.5, F2.2.7 (commits 2026-04-09 à 2026-04-11)
- F2.3.2, F2.3.5 (commit
93ef93e56du 2026-04-14) - F2.4.1, F2.4.3, F2.4.5, F2.5.1, F2.5.2, F2.5.3 (commit
4509038bf) - F2.6.1 (commit
99041f011du 2026-04-09) - F6.1.2, F6.1.3, F6.1.4 (commits 2026-04-22 à 2026-04-26 —
return Truepermissif natif) - F6.2.1, F6.3.1, F6.4.1 (commits 2026-04-25 à 2026-04-26 — états natifs)
- F5.2.1, F5.2.3, F5.2.4, F5.5.1 (chemins clients antérieurs, non touchés < 14 jours)
Pour ces findings, le motif factuel est « introduction native du contrôle déjà à l'état permissif/désactivé », pas une bascule postérieure.