169 lines
6.0 KiB
Python
169 lines
6.0 KiB
Python
"""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,
|
|
},
|
|
}
|