fix: replay routing — lookup machine_id dans replay_states + auto-inject machine_id
- /replay/next cherche dans replay_states par machine_id (pas seulement machine_replay_target) - execute-windows auto-détecte la machine Windows connectée - resolve_target utilise ThreadPool par défaut (pas le GPU executor saturé) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1166,26 +1166,37 @@ async def get_next_action(session_id: str, machine_id: str = "default"):
|
|||||||
with _replay_lock:
|
with _replay_lock:
|
||||||
queue = _replay_queues.get(session_id, [])
|
queue = _replay_queues.get(session_id, [])
|
||||||
|
|
||||||
if not queue:
|
if not queue and machine_id != "default":
|
||||||
# Seul le lookup machine_replay_target est conservé (sûr : mapping explicite
|
# Lookup 1 : machine_replay_target (mapping explicite POST /replay)
|
||||||
# créé lors du POST /replay). Le cross-session stealing a été supprimé
|
target_sid = _machine_replay_target.get(machine_id)
|
||||||
# car il causait des race conditions entre agents.
|
if target_sid and target_sid != session_id:
|
||||||
if machine_id != "default":
|
target_queue = _replay_queues.get(target_sid, [])
|
||||||
target_sid = _machine_replay_target.get(machine_id)
|
if target_queue:
|
||||||
if target_sid and target_sid != session_id:
|
queue = target_queue
|
||||||
target_queue = _replay_queues.get(target_sid, [])
|
_replay_queues[session_id] = target_queue
|
||||||
if target_queue:
|
del _replay_queues[target_sid]
|
||||||
logger.info(
|
for state in _replay_states.values():
|
||||||
f"Replay machine-target: {machine_id} -> "
|
if state["session_id"] == target_sid and state["status"] == "running":
|
||||||
f"transfert queue {target_sid} -> {session_id}"
|
state["session_id"] = session_id
|
||||||
)
|
_machine_replay_target[machine_id] = session_id
|
||||||
queue = target_queue
|
logger.info(f"Replay machine-target: {machine_id} -> {target_sid} -> {session_id}")
|
||||||
_replay_queues[session_id] = target_queue
|
|
||||||
del _replay_queues[target_sid]
|
# Lookup 2 : chercher dans les replay_states actifs pour cette machine
|
||||||
for state in _replay_states.values():
|
if not queue:
|
||||||
if state["session_id"] == target_sid and state["status"] == "running":
|
for state in _replay_states.values():
|
||||||
state["session_id"] = session_id
|
if (state.get("machine_id") == machine_id
|
||||||
_machine_replay_target[machine_id] = session_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:
|
if not queue:
|
||||||
return {"action": None, "session_id": session_id, "machine_id": machine_id}
|
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
|
tmp_path = tmp.name
|
||||||
|
|
||||||
try:
|
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
|
import asyncio
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
result = await loop.run_in_executor(
|
result = await loop.run_in_executor(
|
||||||
_gpu_executor,
|
None, # ThreadPool par défaut (pas _gpu_executor)
|
||||||
_resolve_target_sync,
|
_resolve_target_sync,
|
||||||
tmp_path,
|
tmp_path,
|
||||||
request.target_spec,
|
request.target_spec,
|
||||||
|
|||||||
@@ -969,11 +969,26 @@ def execute_windows():
|
|||||||
if vwb_type in ('keyboard_shortcut', 'hotkey') and 'keys' in params:
|
if vwb_type in ('keyboard_shortcut', 'hotkey') and 'keys' in params:
|
||||||
action['keys'] = params['keys']
|
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:
|
try:
|
||||||
resp = req.post(
|
resp = req.post(
|
||||||
'http://localhost:5005/api/v1/traces/stream/replay/raw',
|
'http://localhost:5005/api/v1/traces/stream/replay/raw',
|
||||||
json=data,
|
json=data,
|
||||||
timeout=30, # Augmenté car le template matching peut prendre du temps
|
timeout=30,
|
||||||
)
|
)
|
||||||
return jsonify(resp.json()), resp.status_code
|
return jsonify(resp.json()), resp.status_code
|
||||||
except req.ConnectionError:
|
except req.ConnectionError:
|
||||||
|
|||||||
Reference in New Issue
Block a user