fix(agent): resilience disparition Lea en RDP/Citrix (watchdog session interactive re-affiche le tray)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-07-01 12:36:47 +02:00
parent 2a1b1ed80e
commit 9ab5ed4671
4 changed files with 545 additions and 49 deletions

View File

@@ -137,6 +137,15 @@ class SmartTrayV1:
self._state_lock = threading.Lock()
self._stop_event = threading.Event()
# Résilience RDP/Citrix : run() peut être rappelé plusieurs fois par le
# watchdog de session (ré-affichage du tray à la reconnexion). Les
# threads de fond (connexion, cache workflows, hotkey) et l'accueil ne
# doivent démarrer QU'UNE fois — sinon on duplique les threads.
self._bg_started = False
# Signalé quand l'utilisateur a demandé Quitter : le watchdog ne doit
# alors PAS relancer le tray.
self._quit_requested = False
# Notifications
self._notifier = NotificationManager()
@@ -709,6 +718,11 @@ class SmartTrayV1:
"""Arrete proprement l'agent et quitte."""
logger.info("Arret demande par l'utilisateur")
# Marquer l'arret volontaire : le watchdog de session ne doit PAS
# relancer le tray après un Quitter explicite (à distinguer d'une
# simple déconnexion RDP où le tray doit revenir tout seul).
self._quit_requested = True
# Arreter la session si en cours
if self.is_recording:
self.on_stop()
@@ -885,17 +899,24 @@ class SmartTrayV1:
# ------------------------------------------------------------------
def run(self) -> None:
"""Demarre le tray, les threads de fond, et entre dans la boucle principale."""
# Notification d'accueil — divulgation IA (Article 50, Reglement IA)
self._notifier.greet()
"""Demarre (ou ré-affiche) le tray et entre dans la boucle pystray.
# Enregistrer le hotkey global Ctrl+Shift+L (toggle chat)
self._start_hotkey()
Ré-entrant : le watchdog de session (session_watchdog.py) rappelle
cette méthode à chaque reconnexion RDP/Citrix pour ré-afficher le
tray + la fenêtre chat. Les initialisations one-shot (accueil,
hotkey, threads de fond connexion/cache) sont protégées par
``_bg_started`` pour ne PAS dupliquer les threads. Seule l'icône
pystray est recréée à chaque appel (l'ancienne est morte avec la
session précédente).
"""
self._start_background_once()
# Tooltip avec identifiant machine pour le multi-machine
tray_title = f"Agent V1 - {self.machine_id}"
# Menu statique — reconstruit via _update_icon() quand l'état change
# Menu statique — reconstruit via _update_icon() quand l'état change.
# Nouvelle icône à chaque (ré)affichage : l'objet pystray précédent
# est invalide une fois sa boucle sortie (session interactive perdue).
self.icon = pystray.Icon(
"AgentV1",
self._current_icon(),
@@ -903,6 +924,33 @@ class SmartTrayV1:
menu=pystray.Menu(*self._get_menu_items()),
)
# Rafraîchir les workflows au (ré)affichage — utile après reconnexion.
if self._bg_started and self.server_client is not None:
threading.Thread(target=self._fetch_workflows, daemon=True).start()
# Boucle principale pystray (bloquante). Sort quand la session
# interactive disparaît (RDP déconnecté) OU sur _on_quit → le
# watchdog décide alors de relancer ou non.
logger.info("SmartTrayV1 demarre — entree dans la boucle pystray")
self.icon.run()
def _start_background_once(self) -> None:
"""Initialisations one-shot : accueil, hotkey, threads de fond.
Idempotent : les appels suivants (ré-affichage tray) sont des no-op.
Garantit qu'on n'accumule pas de threads connexion/cache à chaque
reconnexion RDP.
"""
if self._bg_started:
return
self._bg_started = True
# Notification d'accueil — divulgation IA (Article 50, Reglement IA)
self._notifier.greet()
# Enregistrer le hotkey global Ctrl+Shift+L (toggle chat)
self._start_hotkey()
# Demarrer le thread de verification connexion
if self.server_client is not None:
conn_thread = threading.Thread(
@@ -924,7 +972,3 @@ class SmartTrayV1:
threading.Thread(
target=self._fetch_workflows, daemon=True
).start()
# Boucle principale pystray (bloquante)
logger.info("SmartTrayV1 demarre — entree dans la boucle pystray")
self.icon.run()