fix(vwb): execute-windows route vers la machine la plus active (pas alphabétique)

Quand le frontend ne passe pas de machine_id explicite, le backend VWB
auto-sélectionne une machine Windows en interrogeant /api/v1/traces/
stream/machines. Le code prenait la première de la liste sans tri, donc
l'ordre dépendait de l'ordre arbitraire renvoyé par le streaming server.

Conséquence reproduite ce 2026-05-06 : un replay du workflow Urgence a
été dispatché vers DESKTOP-ST3VBSD_windows alors que l'agent V1 actif
polait depuis DESKTOP-58D5CAC_windows. /replay/next ne dispatchait
aucune action puisque state.machine_id != polling_machine_id.
Symptôme côté Dom : "rien ne se passe sur Windows".

Correction : tri explicite par last_activity desc avant sélection.
La machine retenue est désormais celle qui a heartbeaté le plus
récemment (= celle qui POLLE actuellement le serveur).

Le workflow.machine_id (machine d'origine d'enregistrement) reste
distinct de la cible d'exécution : un workflow enregistré sur PC A
peut être rejoué sur PC B grâce au pipeline 100% visuel qui recalcule
anchors et coordonnées selon la résolution courante. C'était la
vraie intention architecturale, masquée par le bug de tri.
This commit is contained in:
Dom
2026-05-06 20:23:44 +02:00
parent c969f93a23
commit 6fdedbfe9d

View File

@@ -1082,8 +1082,13 @@ def execute_windows():
if not data.get('session_id'):
data['session_id'] = 'agent_demo_user'
# Injecter le machine_id pour le ciblage multi-machine
# Chercher la première machine Windows connectée si pas spécifié
# 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 :
# un workflow enregistré sur PC A doit pouvoir être rejoué sur PC B (vision
# 100 % visuelle, recalcul anchors+coords selon la résolution courante).
# Le workflow.machine_id signale l'origine d'enregistrement, pas la cible
# d'exécution — la cible doit être l'agent qui POLLE actuellement.
if 'machine_id' not in data or not data.get('machine_id'):
try:
machines_resp = req.get(
@@ -1093,11 +1098,19 @@ def execute_windows():
)
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
# Filtrer Windows + non default, trier par last_activity desc
windows_machines = [
m for m in machines
if m.get('machine_id')
and m['machine_id'] != 'default'
and 'windows' in m['machine_id'].lower()
]
windows_machines.sort(
key=lambda m: m.get('last_activity', ''),
reverse=True,
)
if windows_machines:
data['machine_id'] = windows_machines[0]['machine_id']
except Exception:
pass