diff --git a/agent_v0/server_v1/api_stream.py b/agent_v0/server_v1/api_stream.py index e3be254e3..2d794cb61 100644 --- a/agent_v0/server_v1/api_stream.py +++ b/agent_v0/server_v1/api_stream.py @@ -1166,26 +1166,37 @@ async def get_next_action(session_id: str, machine_id: str = "default"): with _replay_lock: queue = _replay_queues.get(session_id, []) - if not queue: - # Seul le lookup machine_replay_target est conservé (sûr : mapping explicite - # créé lors du POST /replay). Le cross-session stealing a été supprimé - # car il causait des race conditions entre agents. - if machine_id != "default": - target_sid = _machine_replay_target.get(machine_id) - if target_sid and target_sid != session_id: - target_queue = _replay_queues.get(target_sid, []) - if target_queue: - logger.info( - f"Replay machine-target: {machine_id} -> " - f"transfert queue {target_sid} -> {session_id}" - ) - queue = target_queue - _replay_queues[session_id] = target_queue - del _replay_queues[target_sid] - for state in _replay_states.values(): - if state["session_id"] == target_sid and state["status"] == "running": - state["session_id"] = session_id - _machine_replay_target[machine_id] = session_id + if not queue and machine_id != "default": + # Lookup 1 : machine_replay_target (mapping explicite POST /replay) + target_sid = _machine_replay_target.get(machine_id) + if target_sid and target_sid != session_id: + target_queue = _replay_queues.get(target_sid, []) + if target_queue: + queue = target_queue + _replay_queues[session_id] = target_queue + del _replay_queues[target_sid] + for state in _replay_states.values(): + if state["session_id"] == target_sid and state["status"] == "running": + state["session_id"] = session_id + _machine_replay_target[machine_id] = session_id + logger.info(f"Replay machine-target: {machine_id} -> {target_sid} -> {session_id}") + + # Lookup 2 : chercher dans les replay_states actifs pour cette machine + if not queue: + for state in _replay_states.values(): + if (state.get("machine_id") == machine_id + and state["status"] == "running" + and state["session_id"] != session_id): + other_sid = state["session_id"] + other_queue = _replay_queues.get(other_sid, []) + if other_queue: + queue = other_queue + _replay_queues[session_id] = other_queue + del _replay_queues[other_sid] + state["session_id"] = session_id + _machine_replay_target[machine_id] = session_id + logger.info(f"Replay machine-state: {machine_id} -> {other_sid} -> {session_id}") + break if not queue: return {"action": None, "session_id": session_id, "machine_id": machine_id} @@ -1603,11 +1614,12 @@ async def resolve_target(request: ResolveTargetRequest): tmp_path = tmp.name try: - # Lancer la résolution visuelle dans le thread GPU + # Lancer la résolution visuelle dans un thread SÉPARÉ (pas le GPU executor + # qui peut être saturé par le SessionWorker). Le template matching est CPU-only. import asyncio loop = asyncio.get_event_loop() result = await loop.run_in_executor( - _gpu_executor, + None, # ThreadPool par défaut (pas _gpu_executor) _resolve_target_sync, tmp_path, request.target_spec, diff --git a/visual_workflow_builder/backend/api_v3/dag_execute.py b/visual_workflow_builder/backend/api_v3/dag_execute.py index 464efa348..3e4c41957 100644 --- a/visual_workflow_builder/backend/api_v3/dag_execute.py +++ b/visual_workflow_builder/backend/api_v3/dag_execute.py @@ -969,11 +969,26 @@ def execute_windows(): if vwb_type in ('keyboard_shortcut', 'hotkey') and 'keys' in params: action['keys'] = params['keys'] + # Injecter le machine_id pour le ciblage multi-machine + # Chercher la première machine Windows connectée si pas spécifié + if 'machine_id' not in data or not data.get('machine_id'): + try: + machines_resp = req.get('http://localhost:5005/api/v1/traces/stream/machines', timeout=3) + if machines_resp.ok: + machines = machines_resp.json().get('machines', []) + for m in machines: + mid = m.get('machine_id', '') + if mid and mid != 'default' and 'windows' in mid.lower(): + data['machine_id'] = mid + break + except Exception: + pass + try: resp = req.post( 'http://localhost:5005/api/v1/traces/stream/replay/raw', json=data, - timeout=30, # Augmenté car le template matching peut prendre du temps + timeout=30, ) return jsonify(resp.json()), resp.status_code except req.ConnectionError: