Suppression du .git embarqué dans agent_v0/ — le code est maintenant tracké normalement dans le repo principal. Inclut : agent_v1 (client), server_v1 (streaming), lea_ui (chat client) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
191 lines
6.3 KiB
Python
191 lines
6.3 KiB
Python
# agent_v1/ui/shared_state.py
|
|
"""
|
|
Etat partage entre le systray et le chat Lea. Thread-safe.
|
|
|
|
Point central de verite pour l'etat de l'agent :
|
|
- Enregistrement en cours (oui/non, nom de la tache)
|
|
- Replay en cours
|
|
- Compteur d'actions
|
|
|
|
Les deux composants UI (SmartTrayV1 et ChatWindow) lisent et ecrivent
|
|
dans cet objet. Chaque changement notifie tous les listeners enregistres.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import threading
|
|
from typing import Any, Callable, List, Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AgentState:
|
|
"""Etat partage entre le systray et le chat Lea. Thread-safe."""
|
|
|
|
def __init__(self) -> None:
|
|
self._lock = threading.Lock()
|
|
|
|
# Etat d'enregistrement
|
|
self._recording = False
|
|
self._recording_name = ""
|
|
self._actions_count = 0
|
|
|
|
# Etat de replay
|
|
self._replay_active = False
|
|
|
|
# Callbacks de demarrage/arret de session (relies au moteur agent)
|
|
self._on_start: Optional[Callable[[str], None]] = None
|
|
self._on_stop: Optional[Callable[[], None]] = None
|
|
|
|
# Listeners notifies a chaque changement d'etat
|
|
self._listeners: List[Callable[["AgentState"], None]] = []
|
|
|
|
# ------------------------------------------------------------------
|
|
# Proprietes en lecture seule (thread-safe)
|
|
# ------------------------------------------------------------------
|
|
|
|
@property
|
|
def is_recording(self) -> bool:
|
|
with self._lock:
|
|
return self._recording
|
|
|
|
@property
|
|
def recording_name(self) -> str:
|
|
with self._lock:
|
|
return self._recording_name
|
|
|
|
@property
|
|
def actions_count(self) -> int:
|
|
with self._lock:
|
|
return self._actions_count
|
|
|
|
@property
|
|
def is_replay_active(self) -> bool:
|
|
with self._lock:
|
|
return self._replay_active
|
|
|
|
# ------------------------------------------------------------------
|
|
# Mutations (thread-safe, notifient les listeners)
|
|
# ------------------------------------------------------------------
|
|
|
|
def start_recording(self, name: str) -> None:
|
|
"""Demarre un enregistrement (appele depuis systray OU chat).
|
|
|
|
Appelle le callback on_start si defini, puis notifie les listeners.
|
|
"""
|
|
with self._lock:
|
|
if self._recording:
|
|
logger.warning("Enregistrement deja en cours, ignore")
|
|
return
|
|
self._recording = True
|
|
self._recording_name = name
|
|
self._actions_count = 0
|
|
on_start = self._on_start
|
|
|
|
logger.info("Enregistrement demarre : %s", name)
|
|
|
|
# Appeler le callback moteur (hors du lock pour eviter deadlock)
|
|
if on_start is not None:
|
|
try:
|
|
on_start(name)
|
|
except Exception as e:
|
|
logger.error("Erreur demarrage session : %s", e)
|
|
# Annuler l'enregistrement si le moteur echoue
|
|
with self._lock:
|
|
self._recording = False
|
|
self._recording_name = ""
|
|
self._notify_listeners()
|
|
raise
|
|
|
|
self._notify_listeners()
|
|
|
|
def stop_recording(self) -> None:
|
|
"""Arrete l'enregistrement (appele depuis systray OU chat).
|
|
|
|
Appelle le callback on_stop si defini, puis notifie les listeners.
|
|
"""
|
|
with self._lock:
|
|
if not self._recording:
|
|
logger.debug("Pas d'enregistrement en cours, ignore")
|
|
return
|
|
self._recording = False
|
|
name = self._recording_name
|
|
count = self._actions_count
|
|
on_stop = self._on_stop
|
|
|
|
logger.info("Enregistrement arrete : %s (%d actions)", name, count)
|
|
|
|
# Appeler le callback moteur
|
|
if on_stop is not None:
|
|
try:
|
|
on_stop()
|
|
except Exception as e:
|
|
logger.error("Erreur arret session : %s", e)
|
|
|
|
self._notify_listeners()
|
|
|
|
def update_actions_count(self, count: int) -> None:
|
|
"""Met a jour le compteur d'actions (appele par le moteur agent)."""
|
|
with self._lock:
|
|
self._actions_count = count
|
|
self._notify_listeners()
|
|
|
|
def set_replay_active(self, active: bool) -> None:
|
|
"""Active ou desactive le mode replay."""
|
|
with self._lock:
|
|
if self._replay_active == active:
|
|
return
|
|
self._replay_active = active
|
|
logger.info("Replay %s", "actif" if active else "termine")
|
|
self._notify_listeners()
|
|
|
|
# ------------------------------------------------------------------
|
|
# Enregistrement des callbacks et listeners
|
|
# ------------------------------------------------------------------
|
|
|
|
def set_on_start(self, callback: Callable[[str], None]) -> None:
|
|
"""Definit le callback appele quand un enregistrement demarre.
|
|
|
|
Ce callback est le pont vers le moteur agent (AgentV1.start_session).
|
|
"""
|
|
with self._lock:
|
|
self._on_start = callback
|
|
|
|
def set_on_stop(self, callback: Callable[[], None]) -> None:
|
|
"""Definit le callback appele quand un enregistrement s'arrete.
|
|
|
|
Ce callback est le pont vers le moteur agent (AgentV1.stop_session).
|
|
"""
|
|
with self._lock:
|
|
self._on_stop = callback
|
|
|
|
def on_change(self, callback: Callable[["AgentState"], None]) -> None:
|
|
"""Enregistre un listener notifie a chaque changement d'etat.
|
|
|
|
Les listeners sont appeles dans un thread separe pour ne pas
|
|
bloquer l'appelant.
|
|
"""
|
|
with self._lock:
|
|
self._listeners.append(callback)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Notification interne
|
|
# ------------------------------------------------------------------
|
|
|
|
def _notify_listeners(self) -> None:
|
|
"""Notifie tous les listeners enregistres du changement d'etat."""
|
|
with self._lock:
|
|
listeners = list(self._listeners)
|
|
|
|
for listener in listeners:
|
|
try:
|
|
# Appel dans un thread pour ne pas bloquer
|
|
threading.Thread(
|
|
target=listener,
|
|
args=(self,),
|
|
daemon=True,
|
|
).start()
|
|
except Exception as e:
|
|
logger.error("Erreur notification listener : %s", e)
|