feat(p1): persist workflows and semantic learning artifacts

This commit is contained in:
Dom
2026-06-02 16:20:38 +02:00
parent 7a1a5cb6fd
commit 86b3c8f7e7
21 changed files with 3816 additions and 31 deletions

View File

@@ -43,6 +43,22 @@ logger = logging.getLogger(__name__)
_MEMORY_SINGLETON: Optional[Any] = None
_MEMORY_DISABLED = False
_GENERIC_BUTTON_TEXTS = {
"annuler",
"cancel",
"enregistrer",
"non",
"no",
"ok",
"oui",
"ouvrir",
"open",
"remplacer",
"replace",
"save",
"yes",
}
def get_memory_store():
"""Retourne le `TargetMemoryStore` partagé, ou None si indisponible.
@@ -91,6 +107,44 @@ def _norm_text(s: str) -> str:
return " ".join(s.split())
def _memory_lookup_skip_reason(target_spec: Dict[str, Any]) -> str:
"""Retourne la raison pour laquelle la mémoire ne doit pas court-circuiter.
Les clics qui changent de fenêtre doivent être résolus visuellement à
l'instant T : une coordonnée apprise peut être une bonne piste, mais pas
une décision finale. Pour les boutons très génériques, on exige au moins
un contexte de fenêtre/interaction dans la clé mémoire afin d'éviter les
collisions entre « Enregistrer », « OK », « Oui », etc.
"""
if not isinstance(target_spec, dict):
return ""
hints = target_spec.get("context_hints") or {}
if bool(hints.get("requires_window_transition")):
return "window_transition_requires_visual_confirmation"
button_text = _norm_text(str(target_spec.get("by_text") or ""))
if button_text not in _GENERIC_BUTTON_TEXTS:
return ""
before = (
hints.get("expected_window_before")
or hints.get("button_expected_before_window")
or hints.get("window_title")
or target_spec.get("window_title")
)
after = (
hints.get("expected_window_after")
or hints.get("button_expected_after_window")
or hints.get("expected_after_window")
)
interaction = hints.get("interaction") or hints.get("foreground_dialog_id")
role = target_spec.get("by_role")
if not (before and role and (after or interaction)):
return "generic_button_missing_context"
return ""
def compute_screen_sig(window_title: str) -> str:
"""Calcule la signature d'écran V4 à partir du titre de fenêtre.
@@ -203,6 +257,11 @@ def memory_lookup(
(resolved, method, x_pct, y_pct, score, ...) si une entrée fiable
est trouvée. None sinon.
"""
skip_reason = _memory_lookup_skip_reason(target_spec)
if skip_reason:
logger.info("memory_lookup SKIP : %s", skip_reason)
return None
store = get_memory_store()
if store is None:
return None