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:
@@ -607,29 +607,20 @@ class AgentV1:
|
||||
def run(self):
|
||||
self.ui.run()
|
||||
|
||||
def _headless_keepalive(agent):
|
||||
"""Maintient le main thread vivant quand l'UI tray ne peut pas tourner.
|
||||
def _install_signal_handlers(agent, watchdog) -> None:
|
||||
"""Installe SIGTERM/SIGINT/SIGBREAK pour un arrêt propre du main thread.
|
||||
|
||||
Sans cela, ``agent.run()`` retourne immédiatement (pystray échoue quand
|
||||
Léa est lancée via SSH sans session interactive Windows), le main thread
|
||||
se termine, et TOUS les daemon threads — y compris ``_replay_poll_loop``
|
||||
— meurent avec lui. Observé 3 fois en 24h les 24/05 :
|
||||
- SSH ``Permission denied`` (1231)
|
||||
- polls morts après relance distante (1620)
|
||||
- polls morts ``replay_sess_506d6fa2`` (1627)
|
||||
|
||||
Le keepalive ne se déclenche QUE si ``agent.run()`` est sorti tout en
|
||||
laissant ``agent.running=True`` (cas anormal). En mode interactif
|
||||
normal, ``pystray.Icon.run()`` ne sort jamais, donc ce code est
|
||||
invisible.
|
||||
Met ``agent.running=False`` (les daemon threads s'arrêtent) et réveille
|
||||
le watchdog (qui sort de sa boucle de surveillance). Sans session
|
||||
interactive (pystray.Icon.stop indisponible), c'est le SEUL moyen
|
||||
d'arrêter Léa proprement : ``kill -TERM <pid>`` ou Ctrl+C.
|
||||
"""
|
||||
import signal as _sig
|
||||
_stop = threading.Event()
|
||||
|
||||
def _handler(sig, frame):
|
||||
logger.info(f"[MAIN] Signal {sig} recu — arret propre")
|
||||
_stop.set()
|
||||
agent.running = False
|
||||
watchdog.stop()
|
||||
|
||||
for sig_name in ("SIGTERM", "SIGINT", "SIGBREAK"):
|
||||
sig_obj = getattr(_sig, sig_name, None)
|
||||
@@ -640,33 +631,49 @@ def _headless_keepalive(agent):
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
|
||||
logger.info(
|
||||
"[MAIN] Keepalive headless actif — main thread bloque pour maintenir "
|
||||
"les daemon threads (_replay_poll_loop, heartbeat, capture_server) vivants. "
|
||||
"Pour stopper Lea : kill -TERM <pid> ou Ctrl+C."
|
||||
)
|
||||
try:
|
||||
_stop.wait()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
agent.running = False
|
||||
logger.info("[MAIN] Keepalive termine — agent.running=False, daemon threads vont s'arreter")
|
||||
|
||||
def _agent_should_live(agent) -> bool:
|
||||
"""Vrai tant que Léa doit vivre : agent actif ET pas de Quitter explicite.
|
||||
|
||||
Un « Quitter » utilisateur (``ui._quit_requested``) doit stopper le
|
||||
watchdog pour de bon ; une simple déconnexion RDP ne met JAMAIS ce flag
|
||||
→ le tray revient tout seul à la reconnexion.
|
||||
"""
|
||||
if not getattr(agent, "running", False):
|
||||
return False
|
||||
ui = getattr(agent, "ui", None)
|
||||
if ui is not None and getattr(ui, "_quit_requested", False):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
agent = AgentV1()
|
||||
try:
|
||||
agent.run()
|
||||
except Exception:
|
||||
logger.exception("[MAIN] agent.run() a leve une exception")
|
||||
from .ui.session_watchdog import InteractiveSessionWatchdog
|
||||
|
||||
if getattr(agent, "running", False):
|
||||
logger.warning(
|
||||
"[MAIN] agent.run() est sorti mais agent.running=True — "
|
||||
"probablement pystray sans session interactive (SSH). "
|
||||
"Bascule en keepalive headless."
|
||||
)
|
||||
_headless_keepalive(agent)
|
||||
agent = AgentV1()
|
||||
|
||||
# Résilience RDP/Citrix : au lieu de bloquer le main thread pour toujours
|
||||
# quand pystray sort (session interactive perdue), on surveille la
|
||||
# session et on ré-affiche le tray + le chat à chaque reconnexion.
|
||||
# agent.run() (== agent.ui.run()) est ré-entrant : les threads de fond
|
||||
# ne démarrent qu'une fois, seule l'icône est recréée. Les daemon threads
|
||||
# de capture/heartbeat/replay tournent contre agent.running et restent
|
||||
# uniques — le watchdog n'y touche pas.
|
||||
watchdog = InteractiveSessionWatchdog(
|
||||
run_ui=agent.run,
|
||||
is_running=lambda: _agent_should_live(agent),
|
||||
)
|
||||
_install_signal_handlers(agent, watchdog)
|
||||
|
||||
try:
|
||||
watchdog.run()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("[MAIN] Interruption clavier — arret propre")
|
||||
except Exception:
|
||||
logger.exception("[MAIN] Le watchdog de session a leve une exception")
|
||||
finally:
|
||||
agent.running = False
|
||||
logger.info("[MAIN] Sortie — agent.running=False, daemon threads vont s'arreter")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user