860 lines
42 KiB
Markdown
860 lines
42 KiB
Markdown
# 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_position` ne s'exécute QUE si `RPA_ENABLE_TEXT_PRECHECK=true`. La fonction reste définie en `resolve_engine.py:2239-2289` mais 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 renvoie `match=True` et 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_popup` est 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 par 40440f1ca à 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` (commit 22c0a2ba6, branche `next_retry == 2` conservé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_embedding` fourni 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_source` est vide ou autre, ou si `window_capture.rect` absent, 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_context` skippé 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_sync` qui 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 si `safety_level`/`safety_checks` déclarés. Ce câblage `execution_mode → supervised` a été corrigé par 7233df2bb.
|
||
|
||
**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)` : ajoute `RPA_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` : restaure `resolved=False` sur drift trop grand (annule le fallback aveugle introduit par b584bbabc) ; é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 ; force `execution_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 sur `verification_failed`/`no_screen_change`.
|
||
- `c969f93a2` (6 mai) — `fix(replay): self-healing Win+D auto au retry 1` : commit de la fonctionnalité, reverté par 22c0a2ba6.
|
||
- `1cbec2806` (6 mai) — `fix(resolve): rebrancher hybrid_text_direct dans _resolve_target_sync` : reconnecte `_resolve_by_ocr_text` dans 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 un `fallback_recorded_coords` (resolved=True). Reverté factuellement par 40440f1ca le 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 pour `template_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 ») :
|
||
|
||
1. **F2.3.1 + F2.6.2** — `agent_v0/server_v1/api_stream.py:4519-4521` : flag `RPA_ENABLE_TEXT_PRECHECK` à défaut `"false"`. C'est le geste d'extinction explicite mentionné dans le commit `56e869c46` ; 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.
|
||
|
||
2. **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é ».
|
||
|
||
3. **F2.6.5** — `agent_v0/server_v1/api_stream.py:3011-3017` : `pause_for_human ignorée (mode autonome) — replay continue`. Si `execution_mode` n'est pas propagé jusqu'au replay (cf. commit 7233df2bb qui 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 message `56e869c46`).
|
||
|
||
|
||
## 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ée `success=True` au 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 pour `policy_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 branche `snapshot.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 :
|
||
```python
|
||
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 :
|
||
```python
|
||
_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 :
|
||
```python
|
||
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) :
|
||
```python
|
||
if img.height < 200 or img.width < 400:
|
||
[...]
|
||
```
|
||
**Après** (`7233df2bb`, 2026-05-07) — seuil élargi à `< 1200×800` :
|
||
```python
|
||
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) :
|
||
```python
|
||
# --- 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) :
|
||
```python
|
||
# --- 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 `4509038bf` du 2026-04-09 et `d5deac302` du 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 `93ef93e56` du 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 `99041f011` du 2026-04-09)
|
||
- F6.1.2, F6.1.3, F6.1.4 (commits 2026-04-22 à 2026-04-26 — `return True` permissif 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.
|