From 7233df2bb9c55fdb1f719369dd4350e05fe21507 Mon Sep 17 00:00:00 2001 From: Dom Date: Thu, 7 May 2026 10:34:29 +0200 Subject: [PATCH] =?UTF-8?q?fix(replay):=20c=C3=A2blage=20execution=5Fmode?= =?UTF-8?q?=20supervised=20+=20seuil=20large=20fallback=20heartbeat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux corrections liées au scenario démo Urgence GHT (workflow lecture multi-onglets + t2a_decision + pause_for_human + saisies dans Codage) : 1. Mode supervised propagé jusqu'au pipeline replay --------------------------------------------------- Symptôme constaté ce 7 mai : Léa lit les onglets, t2a_decision tourne (variable `dec` présente avec decision="FORFAIT_URGENCE"), mais la pause_for_human est SKIPPÉE silencieusement et les saisies type_text s'enchaînent dans le mauvais écran. Cause : api_stream.py:2140 passait `params={}` codé en dur lors de la création du replay_state. Conséquence : le code en aval qui lit `replay_state.params.execution_mode` (api_stream.py:2964) avait toujours le défaut "autonomous" → branche QW4 : # Mode autonome sans safety_checks → skip (comportement legacy) logger.info("pause_for_human ignorée (mode autonome)") Modifications : - RawReplayRequest gagne un champ `params: Optional[Dict[str, Any]]` - start_raw_replay propage `request.params or {}` à _create_replay_state - dag_execute.execute_windows force par défaut `data['params']['execution_mode'] = 'supervised'` quand le frontend ne précise rien (cas démo VWB → Windows). Override possible. Conséquence : la pause_for_human du workflow Urgence déclenche bien la PauseDialog VWB ("Décision : {{dec.decision_court}}"). Le médecin valide ou annule avant que les saisies type_text ne s'exécutent dans Codage. Note pour la démo réelle (post-aujourd'hui) : le scénario crédible veut que Léa soit déclenchée depuis SON chat (port 5004), pas depuis VWB. C'est un autre commit à venir — pour l'instant VWB suffit pour le développement (cf. handoff session). 2. Seuil détection image tronquée élargi ---------------------------------------- Le seuil initial (height < 200 OR width < 400) ne capturait que les cas extrêmes 2560x60 / 600x72. Mais le client envoie aussi 622x856 (Edge en fenêtre réduite ?) qui passait sous le radar. Élargi à height < 800 OR width < 1200 — un écran moderne fait toujours ≥ 1920x1080, donc le seuil est sain. Sans ce fallback élargi, _resolve_target_sync recevait une image trop petite pour matcher l'anchor → cascade VLM hallucinante. --- agent_v0/server_v1/api_stream.py | 12 ++++++++++-- .../backend/api_v3/dag_execute.py | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/agent_v0/server_v1/api_stream.py b/agent_v0/server_v1/api_stream.py index 1f2cbf0e1..e8338d144 100644 --- a/agent_v0/server_v1/api_stream.py +++ b/agent_v0/server_v1/api_stream.py @@ -563,6 +563,11 @@ class RawReplayRequest(BaseModel): session_id: str = "" machine_id: Optional[str] = None # Machine cible (multi-machine) task_description: str = "" + # Paramètres runtime du replay (lus dans replay_state.params côté pipeline). + # Notamment execution_mode : "autonomous" (défaut, pause_for_human skippée) + # ou "supervised" (pause_for_human bloque jusqu'à validation humaine via + # PauseDialog VWB). Cf. replay_engine.py / api_stream.py:2964. + params: Optional[Dict[str, Any]] = None class SingleActionRequest(BaseModel): @@ -2137,7 +2142,7 @@ async def start_raw_replay(request: RawReplayRequest): workflow_id=f"free_task:{task[:50]}", session_id=session_id, total_actions=len(actions), - params={}, + params=dict(request.params or {}), machine_id=resolved_machine_id, actions=actions, ) @@ -4411,7 +4416,10 @@ async def resolve_target(request: ResolveTargetRequest): # par le dernier heartbeat avant la cascade _resolve_target_sync. effective_w = request.screen_width effective_h = request.screen_height - if img.height < 200 or img.width < 400: + # Seuil large : un écran moderne fait 2560x1600 ou plus. Tout en dessous + # de 1200x800 est suspect — bug client mss.monitors[1] qui crop sur + # barre des tâches (2560x60), Edge fenêtré (622x856), etc. + if img.height < 800 or img.width < 1200: logger.warning( "[RESOLVE_TARGET] Image client tronquée %dx%d (declared %dx%d) — " "fallback heartbeat full screen", diff --git a/visual_workflow_builder/backend/api_v3/dag_execute.py b/visual_workflow_builder/backend/api_v3/dag_execute.py index cafb291ee..7803a7773 100644 --- a/visual_workflow_builder/backend/api_v3/dag_execute.py +++ b/visual_workflow_builder/backend/api_v3/dag_execute.py @@ -1082,6 +1082,15 @@ def execute_windows(): if not data.get('session_id'): data['session_id'] = 'agent_demo_user' + # Forcer le mode supervisé : pause_for_human DÉCLENCHE au lieu d'être + # skippée. Le médecin valide la décision Léa avant que les saisies + # type_text ne s'exécutent dans l'onglet Codage. Crucial pour la démo + # GHT : Léa propose, humain valide, Léa finalise (cf. workflow Urgence). + # Sans ça, mode "autonomous" par défaut → pause skippée → saisies + # tentées sans validation → désordre visuel. + data.setdefault('params', {}) + data['params'].setdefault('execution_mode', 'supervised') + # Injecter le machine_id pour le ciblage multi-machine. # Cibler la machine Windows la plus récemment active (heartbeat last_activity) # plutôt que la première dans l'ordre arbitraire renvoyé par /machines :