diff --git a/agent_v0/agent_v1/main.py b/agent_v0/agent_v1/main.py index ff8cc509a..516eaa55d 100644 --- a/agent_v0/agent_v1/main.py +++ b/agent_v0/agent_v1/main.py @@ -571,9 +571,67 @@ 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. + + 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. + """ + 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 + + for sig_name in ("SIGTERM", "SIGINT", "SIGBREAK"): + sig_obj = getattr(_sig, sig_name, None) + if sig_obj is None: + continue + try: + _sig.signal(sig_obj, _handler) + 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 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 main(): agent = AgentV1() - agent.run() + try: + agent.run() + except Exception: + logger.exception("[MAIN] agent.run() a leve une exception") + + 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) + if __name__ == "__main__": main()