Nouvelle action native VWB qui force le replay à basculer en paused_need_help
avec un message custom. Quand Léa atteint cette étape, elle ne tente pas
d'exécuter — elle pose immédiatement le state, ce qui déclenche la bulle
interactive ChatWindow (J3.5) avec boutons Continuer / Annuler.
Asset démo majeur GHT Sud 95 : permet de scénariser le moment "Léa doute"
au bon endroit dans le workflow, sans dépendre d'un échec aléatoire.
Chaîne complète :
- VWB UI (types.ts) : nouvelle entrée ACTIONS catégorie 'logic', icône ⏸,
paramètre 'message' éditable (textarea).
- Bridge VWB → core (learned_workflow_bridge.py) : passthrough du type +
préservation du message dans parameters.
- Pipeline replay (replay_engine.py) : type ajouté à _ALLOWED_ACTION_TYPES,
conversion edge → action normalisée préserve le message.
- Streaming server (api_stream.py /replay/next) : interception avant envoi
à l'Agent V1 → bascule state en paused_need_help avec pause_message,
retourne {action: None, replay_paused: True}.
- L'action n'est jamais transmise à l'Agent V1 — pure logique serveur.
10 nouveaux tests pytest. Total branche : 57/57 verts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
4.7 KiB
Python
132 lines
4.7 KiB
Python
"""Tests de l'action pause_for_human (C.5).
|
|
|
|
Vérifie la chaîne :
|
|
- Validation côté replay_engine accepte le nouveau type
|
|
- Conversion edge → action normalisée préserve le message
|
|
- Bridge VWB → core mappe correctement
|
|
- Le bridge VWB construit bien un edge avec action.type='pause_for_human'
|
|
"""
|
|
|
|
from agent_v0.server_v1.replay_engine import (
|
|
_ALLOWED_ACTION_TYPES,
|
|
_validate_replay_action,
|
|
_edge_to_normalized_actions,
|
|
)
|
|
from visual_workflow_builder.backend.services.learned_workflow_bridge import (
|
|
VWB_ACTION_TO_CORE,
|
|
convert_vwb_to_core_workflow,
|
|
_vwb_params_to_core,
|
|
)
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Validation pipeline (replay_engine)
|
|
# ----------------------------------------------------------------------
|
|
|
|
def test_pause_for_human_in_allowed_types():
|
|
assert "pause_for_human" in _ALLOWED_ACTION_TYPES
|
|
|
|
|
|
def test_validate_pause_for_human_action_valid():
|
|
action = {"type": "pause_for_human", "parameters": {"message": "Valider UHCD ?"}}
|
|
assert _validate_replay_action(action) is None
|
|
|
|
|
|
def test_validate_pause_for_human_no_params_still_valid():
|
|
"""Le validateur ne doit pas exiger 'message' (fallback côté handler)."""
|
|
action = {"type": "pause_for_human"}
|
|
assert _validate_replay_action(action) is None
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Conversion edge → action normalisée
|
|
# ----------------------------------------------------------------------
|
|
|
|
class _FakeAction:
|
|
def __init__(self, type_, parameters=None):
|
|
self.type = type_
|
|
self.target = None
|
|
self.parameters = parameters or {}
|
|
|
|
|
|
class _FakeEdge:
|
|
def __init__(self, action, edge_id="e1", from_node="n1", to_node="n2"):
|
|
self.edge_id = edge_id
|
|
self.from_node = from_node
|
|
self.to_node = to_node
|
|
self.action = action
|
|
|
|
|
|
def test_edge_to_action_pause_for_human_preserves_message():
|
|
edge = _FakeEdge(_FakeAction(
|
|
"pause_for_human",
|
|
parameters={"message": "Tu valides UHCD ?"},
|
|
))
|
|
actions = _edge_to_normalized_actions(edge, params={})
|
|
assert len(actions) == 1
|
|
a = actions[0]
|
|
assert a["type"] == "pause_for_human"
|
|
assert a["parameters"]["message"] == "Tu valides UHCD ?"
|
|
assert "x_pct" not in a # action logique, pas de coords
|
|
assert "y_pct" not in a
|
|
|
|
|
|
def test_edge_to_action_pause_for_human_default_message():
|
|
edge = _FakeEdge(_FakeAction("pause_for_human", parameters={}))
|
|
actions = _edge_to_normalized_actions(edge, params={})
|
|
assert actions[0]["parameters"]["message"] == "Validation requise"
|
|
|
|
|
|
def test_edge_to_action_pause_for_human_carries_edge_metadata():
|
|
edge = _FakeEdge(
|
|
_FakeAction("pause_for_human", parameters={"message": "x"}),
|
|
edge_id="edge_42", from_node="n_src", to_node="n_dst",
|
|
)
|
|
actions = _edge_to_normalized_actions(edge, params={})
|
|
a = actions[0]
|
|
assert a["edge_id"] == "edge_42"
|
|
assert a["from_node"] == "n_src"
|
|
assert a["to_node"] == "n_dst"
|
|
assert "action_id" in a
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Bridge VWB → core
|
|
# ----------------------------------------------------------------------
|
|
|
|
def test_vwb_action_to_core_passthrough():
|
|
assert VWB_ACTION_TO_CORE["pause_for_human"] == "pause_for_human"
|
|
|
|
|
|
def test_vwb_params_to_core_preserves_message():
|
|
core_params = _vwb_params_to_core("pause_for_human", {"message": "Coucou"})
|
|
assert core_params == {"message": "Coucou"}
|
|
|
|
|
|
def test_vwb_params_to_core_default_message():
|
|
core_params = _vwb_params_to_core("pause_for_human", {})
|
|
assert core_params["message"] == "Validation requise"
|
|
|
|
|
|
def test_export_vwb_workflow_with_pause_step():
|
|
"""Un workflow VWB contenant une step pause_for_human doit produire un edge
|
|
avec action.type='pause_for_human' et message dans parameters."""
|
|
workflow_data = {"id": "wf_demo", "name": "Demo Urgences", "description": ""}
|
|
steps_data = [
|
|
{"id": "s1", "action_type": "click_anchor", "parameters": {"target_text": "25003284"}, "label": "Clic IPP"},
|
|
{"id": "s2", "action_type": "pause_for_human", "parameters": {"message": "Valider UHCD ?"}, "label": "Pause"},
|
|
{"id": "s3", "action_type": "click_anchor", "parameters": {"target_text": "Enregistrer"}, "label": "Clic Enregistrer"},
|
|
]
|
|
core = convert_vwb_to_core_workflow(workflow_data, steps_data)
|
|
assert core["learning_state"] == "COACHING"
|
|
assert len(core["nodes"]) == 3
|
|
assert len(core["edges"]) == 2
|
|
|
|
# L'edge sortant du node de pause doit avoir le bon type + message
|
|
pause_edges = [
|
|
e for e in core["edges"]
|
|
if e["action"]["type"] == "pause_for_human"
|
|
]
|
|
assert len(pause_edges) == 1
|
|
assert pause_edges[0]["action"]["parameters"]["message"] == "Valider UHCD ?"
|