fix: no_screen_change strict → pause supervisée pour apprentissage
Rectification de la branche C introduite dansa21f1ea9f. ## Ce qui était fauxa21f1ea9ffaisait : strict + no_screen_change → retry × 3 → status=error → queue vidée C'est le réflexe d'un RPA classique qui se casse la figure quand ça rate. Ce n'est PAS la philosophie Léa. Dom m'a rappelé que j'avais oublié ma propre vision documentée dans project_lea_apprentissage_plan.md et feedback_not_a_click_box.md : *"Quand elle dit qu'elle n'a pas trouvé X, elle demande montre-moi. C'est à ce moment qu'il faudrait passer en mode apprentissage."* ## Ce qui est correct maintenant strict + no_screen_change → status = "paused_need_help" → failed_action stocké (target, screenshot, method, score, reason) → pause_message demandant l'intervention humaine → queue intacte (l'action reste en tête, prête à être relancée) → log_replay_failure pour l'apprentissage futur → l'agent reçoit replay_paused=True dans /replay/next et s'arrête → l'humain corrige physiquement sur la machine cible → le replay reprend via /replay/{replay_id}/resume Redirection vers le mécanisme paused_need_help qui existe déjà pour le cas target_not_found. Zéro nouveau code de pause, juste une 2ème entrée dans ce mécanisme. Le comportement legacy (success_strict=False) reste inchangé : on log un warning et on continue, comportement tolérant pour les actions non-critiques. ## Lesson apprises 1. Toujours relire les fichiers mémoire pertinents AVANT d'implémenter une branche de gestion d'erreur (nouvelle règle dans feedback_reread_before_code.md) 2. Un échec Léa n'est jamais un "stop avec error" — c'est un moment pédagogique (nouvelle règle dans feedback_failure_is_learning.md) 3. Ne pas s'auto-presser quand Dom n'a jamais demandé d'aller vite ## Tests - 56 tests E2E + Phase0 passent, 0 régression - Comportement vérifié par inspection du code : pause_message formé correctement, queue préservée, log_replay_failure appelé Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3173,56 +3173,84 @@ async def report_action_result(report: ReplayResultReport):
|
||||
elif not report.success and agent_warning == "no_screen_change":
|
||||
# L'action a été exécutée mais l'écran n'a pas changé.
|
||||
#
|
||||
# Philosophie Léa (feedback_failure_is_learning.md) : un échec
|
||||
# n'est jamais un stop avec error — c'est un **moment pédagogique**.
|
||||
# Léa demande à l'humain de montrer ce qu'elle aurait dû faire.
|
||||
#
|
||||
# Comportement legacy (success_strict=False) : loguer l'échec
|
||||
# et continuer vers l'action suivante. Justifié pour les
|
||||
# workflows tolérants où un clic "sans effet" peut être normal
|
||||
# (ex: cliquer sur une case déjà cochée).
|
||||
#
|
||||
# Comportement strict (success_strict=True) : écran inchangé =
|
||||
# vraie erreur. On déclenche un retry, puis stop après épuisement.
|
||||
# Justifié pour les workflows critiques où chaque action doit
|
||||
# produire un effet visuel observable.
|
||||
# "je n'ai pas su faire". On redirige vers le mécanisme de pause
|
||||
# supervisée existant (paused_need_help) pour que Léa demande à
|
||||
# l'humain de montrer. Pas de retry automatique, pas de stop —
|
||||
# on laisse la queue intacte et on attend l'intervention.
|
||||
_is_strict = False
|
||||
_intent_strict = ""
|
||||
_idx_strict = replay_state.get("current_action_index", 0)
|
||||
_actions_meta_strict = replay_state.get("actions", [])
|
||||
if 0 <= _idx_strict < len(_actions_meta_strict):
|
||||
_current_strict = _actions_meta_strict[_idx_strict] or {}
|
||||
_is_strict = bool(_current_strict.get("success_strict", False))
|
||||
_intent_strict = str(_current_strict.get("intention", "") or "")
|
||||
|
||||
if _is_strict and retry_count < MAX_RETRIES_PER_ACTION:
|
||||
# Strict + retries restants : on réinjecte l'action
|
||||
logger.warning(
|
||||
f"Action {action_id} STRICT : écran inchangé — "
|
||||
f"retry {retry_count + 1}/{MAX_RETRIES_PER_ACTION}"
|
||||
if _is_strict:
|
||||
# Apprentissage supervisé : pause, demande d'intervention
|
||||
_tspec = (original_action or {}).get("target_spec") or report.target_spec or {}
|
||||
_target_desc = (
|
||||
_intent_strict
|
||||
or _tspec.get("by_text", "")
|
||||
or _tspec.get("vlm_description", "")[:80]
|
||||
or "cette action"
|
||||
)
|
||||
_schedule_retry(
|
||||
session_id, replay_state,
|
||||
original_action or {"action_id": action_id},
|
||||
retry_count, "no_screen_change_strict",
|
||||
replay_state["status"] = "paused_need_help"
|
||||
replay_state["failed_action"] = {
|
||||
"action_id": action_id,
|
||||
"type": (original_action or {}).get("type", "unknown"),
|
||||
"target_description": _target_desc,
|
||||
"screenshot_b64": screenshot_after or report.screenshot,
|
||||
"target_spec": _tspec,
|
||||
"reason": "no_screen_change_strict",
|
||||
"resolution_method": report.resolution_method or "",
|
||||
"resolution_score": report.resolution_score or 0,
|
||||
}
|
||||
replay_state["pause_message"] = (
|
||||
f"Mon clic sur '{_target_desc}' n'a produit aucun effet. "
|
||||
f"Peux-tu me montrer où je devais cliquer ?"
|
||||
)
|
||||
elif _is_strict:
|
||||
# Strict + retries épuisés : échec définitif, stop replay
|
||||
replay_state["failed_actions"] += 1
|
||||
replay_state["unverified_actions"] += 1
|
||||
error_entry = {
|
||||
"action_id": action_id,
|
||||
"error": "no_screen_change after strict retries",
|
||||
"error": f"no_screen_change_strict: {_target_desc}",
|
||||
"retry_count": retry_count,
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
replay_state["error_log"].append(error_entry)
|
||||
replay_state["status"] = "error"
|
||||
_replay_queues[session_id] = []
|
||||
logger.error(
|
||||
f"Replay {replay_state['replay_id']} STRICT échoué : "
|
||||
f"action {action_id} écran inchangé après {retry_count} retries"
|
||||
logger.warning(
|
||||
f"Replay PAUSE supervisée (apprentissage) : {action_id} "
|
||||
f"écran inchangé sur '{_target_desc}' — en attente "
|
||||
f"d'intervention humaine"
|
||||
)
|
||||
_notify_error_callback(
|
||||
replay_state, action_id,
|
||||
"no_screen_change after strict retries",
|
||||
# Logger l'échec pour l'apprentissage futur
|
||||
try:
|
||||
log_replay_failure(
|
||||
replay_id=replay_state["replay_id"],
|
||||
action_id=action_id,
|
||||
target_spec=_tspec,
|
||||
screenshot_b64=screenshot_after or report.screenshot,
|
||||
error="no_screen_change_strict",
|
||||
extra={
|
||||
"target_description": _target_desc,
|
||||
"resolution_method": report.resolution_method or "",
|
||||
"resolution_score": report.resolution_score or 0,
|
||||
"actions_completed": replay_state["completed_actions"],
|
||||
},
|
||||
)
|
||||
except Exception as _log_exc:
|
||||
logger.debug("log_replay_failure skip: %s", _log_exc)
|
||||
else:
|
||||
# Legacy : on continue
|
||||
# Legacy (non-strict) : on continue, comportement historique
|
||||
replay_state["unverified_actions"] += 1
|
||||
replay_state["completed_actions"] += 1
|
||||
replay_state["current_action_index"] += 1
|
||||
|
||||
Reference in New Issue
Block a user