"""Convert persisted competence YAML files into supervised replay actions.""" from __future__ import annotations from pathlib import Path from typing import Any, Iterable from .catalog import DEFAULT_COMPETENCE_ROOT, CompetenceSummary, load_competences def find_competence( competence_id: str, *, root: Path | str = DEFAULT_COMPETENCE_ROOT, states: Iterable[str] | None = None, ) -> CompetenceSummary: """Find one competence by id across persisted YAML states.""" for competence in load_competences(root=root, states=states): if competence.id == competence_id: return competence raise KeyError(f"Competence '{competence_id}' not found") def build_competence_replay_actions( competence_id: str, *, root: Path | str = DEFAULT_COMPETENCE_ROOT, supervised: bool = True, ) -> list[dict[str, Any]]: """Build Agent V1 raw replay actions for a competence. Candidate competences are intentionally wrapped with human pauses. This makes the first runtime pass an explicit supervised test instead of an autonomous assertion that the competence is already stable. """ competence = find_competence(competence_id, root=root) actions: list[dict[str, Any]] = [] if supervised: actions.append(_pause_action(competence, phase="before")) for index, method in enumerate(competence.methods, start=1): action = _method_to_replay_action(competence, method, index) if action: actions.append(action) if supervised: actions.append(_pause_action(competence, phase="after")) return actions def build_competence_replay_payload( competence_id: str, *, root: Path | str = DEFAULT_COMPETENCE_ROOT, supervised: bool = True, machine_id: str | None = None, session_id: str | None = None, ) -> dict[str, Any]: """Build the payload expected by `/api/v1/traces/stream/replay/raw`.""" competence = find_competence(competence_id, root=root) actions = build_competence_replay_actions(competence_id, root=root, supervised=supervised) payload: dict[str, Any] = { "actions": actions, "task_description": f"Test compétence Léa: {competence.intent_fr}", "params": { "execution_mode": "supervised" if supervised else "autonomous", "competence_id": competence.id, "learning_state": competence.learning_state, }, } if machine_id: payload["machine_id"] = machine_id if session_id: payload["session_id"] = session_id return payload def _method_to_replay_action( competence: CompetenceSummary, method: dict[str, Any], index: int, ) -> dict[str, Any] | None: kind = method.get("kind") params = method.get("parameters") if isinstance(method.get("parameters"), dict) else {} action_id = f"competence_{competence.id}_{index}_{kind or 'step'}" if kind == "key_combo": keys = params.get("keys") if not isinstance(keys, list) or not keys: return None return { "action_id": action_id, "type": "key_combo", "keys": [str(key) for key in keys], "intention": competence.intent_fr, "competence_id": competence.id, "source_method_id": method.get("id"), } if kind == "wait_state": expected = params.get("expected_state") if isinstance(params.get("expected_state"), dict) else {} titles = expected.get("window_title_in") if isinstance(expected.get("window_title_in"), list) else [] timeout_ms = params.get("timeout_ms") if isinstance(params.get("timeout_ms"), int) else 5000 if titles: return { "action_id": action_id, "type": "verify_screen", "expected_node": f"competence:{competence.id}:wait_state", "expected_window_title_contains": [str(title) for title in titles], "timeout_ms": timeout_ms, "intention": competence.intent_fr, "competence_id": competence.id, "source_method_id": method.get("id"), "expected_state": expected, } return { "action_id": action_id, "type": "wait", "duration_ms": min(timeout_ms, 5000), "intention": competence.intent_fr, "competence_id": competence.id, "source_method_id": method.get("id"), } return None def _pause_action(competence: CompetenceSummary, *, phase: str) -> dict[str, Any]: failure = competence.failure_message_template gaps = ", ".join(str(gap.get("id")) for gap in competence.t2_known_gaps if gap.get("id")) if phase == "before": message = ( f"Prépare le test supervisé de la compétence '{competence.id}'. " f"Intention: {competence.intent_fr}. " f"Attendu: {failure.get('attendu', 'état attendu non renseigné')}." ) if gaps: message += f" Points à surveiller: {gaps}." else: message = ( f"Valide le résultat de la compétence '{competence.id}'. " f"Intention: {failure.get('intention', competence.intent_fr)}. " f"Attendu: {failure.get('attendu', 'état attendu non renseigné')}. " "Indique si Léa peut enregistrer ce test comme succès supervisé ou si une correction est nécessaire." ) return { "action_id": f"competence_{competence.id}_pause_{phase}", "type": "pause_for_human", "competence_id": competence.id, "parameters": { "message": message, "intention": failure.get("intention", competence.intent_fr), "attendu": failure.get("attendu", ""), "demande": failure.get("demande", ""), "phase": phase, "verdict_required": phase == "after", "verdict_endpoint": f"/api/v1/lea/competences/{competence.id}/verdict", "competence_id": competence.id, "write_back_enabled": False, }, }