feat: conformité AI Act — divulgation IA, consentement, rétention, arrêt urgence
- Léa se présente comme "assistante basée sur l'intelligence artificielle" - Dialog consentement avant enregistrement (capture écran/clavier) - Rétention logs 180 jours (Article 12 + 26(6)) - Bouton ARRÊT D'URGENCE toujours visible (Article 14) - Transparence mode autonome explicite (Article 50) - Rapport conformité AI Act en français (docs/CONFORMITE_AI_ACT.md) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,11 @@ SCREENSHOT_QUALITY = 85
|
||||
# Désactiver avec RPA_BLUR_SENSITIVE=false pour le développement/tests
|
||||
BLUR_SENSITIVE = os.environ.get("RPA_BLUR_SENSITIVE", "true").lower() in ("true", "1", "yes")
|
||||
|
||||
# Retention des logs — minimum 6 mois (180 jours) requis par le Reglement IA
|
||||
# (Article 12 — journalisation automatique, Article 26(6) — conservation minimum)
|
||||
# Configurable via variable d'environnement pour permettre l'ajustement
|
||||
LOG_RETENTION_DAYS = int(os.environ.get("RPA_LOG_RETENTION_DAYS", "180"))
|
||||
|
||||
# Monitoring
|
||||
PERF_MONITOR_INTERVAL_S = 30
|
||||
LOGS_DIR = BASE_DIR / "logs"
|
||||
|
||||
@@ -14,7 +14,7 @@ import uuid
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
from .config import SESSIONS_ROOT, AGENT_VERSION, SERVER_URL, MACHINE_ID
|
||||
from .config import SESSIONS_ROOT, AGENT_VERSION, SERVER_URL, MACHINE_ID, LOG_RETENTION_DAYS
|
||||
from .core.captor import EventCaptorV1
|
||||
from .core.executor import ActionExecutorV1
|
||||
from .network.streamer import TraceStreamer
|
||||
@@ -51,7 +51,8 @@ class AgentV1:
|
||||
self.session_dir = None
|
||||
|
||||
# Gestion du stockage local et nettoyage
|
||||
self.storage = SessionStorage(SESSIONS_ROOT)
|
||||
# Retention minimum 6 mois (Reglement IA, Article 12)
|
||||
self.storage = SessionStorage(SESSIONS_ROOT, retention_days=LOG_RETENTION_DAYS)
|
||||
threading.Thread(target=self._delayed_cleanup, daemon=True).start()
|
||||
|
||||
self.vision = None
|
||||
|
||||
@@ -14,7 +14,16 @@ from datetime import datetime, timedelta
|
||||
logger = logging.getLogger("session_storage")
|
||||
|
||||
class SessionStorage:
|
||||
def __init__(self, base_dir: Path, max_size_gb: int = 5, retention_days: int = 1):
|
||||
def __init__(self, base_dir: Path, max_size_gb: int = 5, retention_days: int = 180):
|
||||
"""Gestionnaire de stockage local pour les sessions Agent V1.
|
||||
|
||||
Args:
|
||||
base_dir: Dossier racine de stockage des sessions.
|
||||
max_size_gb: Taille maximale du stockage local (Go).
|
||||
retention_days: Duree de retention en jours. Defaut = 180 (6 mois),
|
||||
minimum requis par le Reglement IA (Article 12 — journalisation
|
||||
automatique, Article 26(6) — conservation des logs).
|
||||
"""
|
||||
self.base_dir = base_dir
|
||||
self.max_size_bytes = max_size_gb * 1024 * 1024 * 1024
|
||||
self.retention_days = retention_days
|
||||
|
||||
@@ -247,9 +247,10 @@ class ChatWindow:
|
||||
self._build_input_area(root)
|
||||
self._build_resize_grip(root)
|
||||
|
||||
# Message d'accueil
|
||||
# Message d'accueil — divulgation IA obligatoire (Article 50, Reglement IA)
|
||||
self._add_lea_message(
|
||||
"Bonjour ! Je suis L\u00e9a.\n"
|
||||
"Bonjour ! Je suis L\u00e9a, une assistante bas\u00e9e sur "
|
||||
"l'intelligence artificielle.\n"
|
||||
"Je peux apprendre vos t\u00e2ches r\u00e9p\u00e9titives "
|
||||
"et les refaire \u00e0 votre place.\n"
|
||||
"Que puis-je faire pour vous ?"
|
||||
@@ -915,11 +916,33 @@ class ChatWindow:
|
||||
)
|
||||
|
||||
def _do_quick_record(self) -> None:
|
||||
"""Demande le nom de la t\u00e2che et lance l'enregistrement."""
|
||||
import tkinter as tk
|
||||
from tkinter import simpledialog
|
||||
"""Demande le consentement puis le nom de la tache et lance l'enregistrement.
|
||||
|
||||
# Creer un dialogue ephemere
|
||||
Notification prealable obligatoire (Articles 13/14, Reglement IA) :
|
||||
l'utilisateur doit etre informe de ce qui sera capture AVANT le demarrage.
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import simpledialog, messagebox
|
||||
|
||||
# --- Consentement prealable (Articles 13/14, Reglement IA) ---
|
||||
consent_root = tk.Tk()
|
||||
consent_root.withdraw()
|
||||
consent_root.attributes('-topmost', True)
|
||||
consent = messagebox.askyesno(
|
||||
"Enregistrement — Information",
|
||||
"\u26a0\ufe0f L'enregistrement va capturer votre \u00e9cran, "
|
||||
"vos clics et vos frappes clavier pour apprendre cette t\u00e2che.\n\n"
|
||||
"Les donn\u00e9es sensibles seront automatiquement flout\u00e9es.\n\n"
|
||||
"Voulez-vous continuer ?",
|
||||
parent=consent_root,
|
||||
)
|
||||
consent_root.destroy()
|
||||
|
||||
if not consent:
|
||||
self._add_lea_message("Enregistrement annul\u00e9.")
|
||||
return
|
||||
|
||||
# --- Dialogue de saisie du nom ---
|
||||
tmp_root = tk.Tk()
|
||||
tmp_root.withdraw()
|
||||
tmp_root.attributes('-topmost', True)
|
||||
|
||||
@@ -120,11 +120,17 @@ class NotificationManager:
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def greet(self) -> bool:
|
||||
"""Notification de bienvenue au démarrage."""
|
||||
"""Notification de bienvenue au démarrage.
|
||||
|
||||
Inclut la divulgation IA obligatoire (Article 50, Règlement IA).
|
||||
"""
|
||||
return self.notify(
|
||||
title=APP_NAME,
|
||||
message="Bonjour ! Léa est prête.",
|
||||
timeout=5,
|
||||
message=(
|
||||
"Bonjour ! Léa est prête. "
|
||||
"Je suis une assistante basée sur l'intelligence artificielle."
|
||||
),
|
||||
timeout=7,
|
||||
)
|
||||
|
||||
def session_started(self, workflow_name: str) -> bool:
|
||||
@@ -152,11 +158,18 @@ class NotificationManager:
|
||||
)
|
||||
|
||||
def replay_started(self, workflow_name: str, step_count: int) -> bool:
|
||||
"""Notification de début de replay."""
|
||||
"""Notification de début de replay.
|
||||
|
||||
Transparence obligatoire en mode autonome (Article 50, Règlement IA) :
|
||||
l'utilisateur doit savoir qu'un système d'IA agit sur son écran.
|
||||
"""
|
||||
return self.notify(
|
||||
title=APP_NAME,
|
||||
message=f"Je m'en occupe ! '{workflow_name}' en cours...",
|
||||
timeout=5,
|
||||
message=(
|
||||
f"Le système d'intelligence artificielle exécute la tâche "
|
||||
f"'{workflow_name}' sur votre écran."
|
||||
),
|
||||
timeout=7,
|
||||
)
|
||||
|
||||
def replay_step(self, current: int, total: int, description: str) -> bool:
|
||||
|
||||
@@ -71,6 +71,23 @@ def _show_info(title: str, message: str) -> None:
|
||||
root.destroy()
|
||||
|
||||
|
||||
def _ask_consent(title: str, message: str) -> bool:
|
||||
"""Dialogue de consentement Oui/Non via tkinter (sans PyQt5).
|
||||
|
||||
Utilise pour la notification prealable obligatoire (Articles 13/14,
|
||||
Reglement IA) avant tout enregistrement.
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
root.attributes('-topmost', True)
|
||||
result = messagebox.askyesno(title, message, parent=root)
|
||||
root.destroy()
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SmartTrayV1
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -240,6 +257,13 @@ class SmartTrayV1:
|
||||
visible=lambda _i: self._chat_window is not None,
|
||||
),
|
||||
pystray.Menu.SEPARATOR,
|
||||
# --- Arret d'urgence (Article 14, Reglement IA — controle humain) ---
|
||||
# Toujours visible, quel que soit l'etat de l'agent
|
||||
item(
|
||||
"\u26d4 ARR\u00caT D'URGENCE",
|
||||
self._on_emergency_stop,
|
||||
),
|
||||
pystray.Menu.SEPARATOR,
|
||||
# --- Utilitaires ---
|
||||
item("\U0001f4c2 Mes fichiers", self._on_open_folder),
|
||||
item("\u274c Quitter L\u00e9a", self._on_quit),
|
||||
@@ -323,9 +347,23 @@ class SmartTrayV1:
|
||||
self._update_icon()
|
||||
|
||||
def _on_start_session(self, _icon=None, _item=None) -> None:
|
||||
"""Demande le nom de la t\u00e2che et demarre la session."""
|
||||
"""Demande le consentement puis le nom de la tache et demarre la session.
|
||||
|
||||
Notification prealable obligatoire (Articles 13/14, Reglement IA) :
|
||||
l'utilisateur doit etre informe de ce qui sera capture AVANT le demarrage.
|
||||
"""
|
||||
# Dialogue tkinter dans un thread dedie
|
||||
def _dialog():
|
||||
# --- Consentement prealable (Articles 13/14, Reglement IA) ---
|
||||
if not _ask_consent(
|
||||
"Enregistrement — Information",
|
||||
"\u26a0\ufe0f L'enregistrement va capturer votre \u00e9cran, "
|
||||
"vos clics et vos frappes clavier pour apprendre cette t\u00e2che.\n\n"
|
||||
"Les donn\u00e9es sensibles seront automatiquement flout\u00e9es.\n\n"
|
||||
"Voulez-vous continuer ?",
|
||||
):
|
||||
return
|
||||
|
||||
name = _ask_string(
|
||||
"Nouvelle t\u00e2che",
|
||||
"D\u00e9crivez la t\u00e2che \u00e0 apprendre :",
|
||||
@@ -427,9 +465,11 @@ class SmartTrayV1:
|
||||
with self._state_lock:
|
||||
self._replay_active = True
|
||||
self._update_icon()
|
||||
# Transparence mode autonome (Article 50, Reglement IA)
|
||||
self._notifier.notify(
|
||||
"L\u00e9a",
|
||||
f"Je m'en occupe ! '{workflow_name}' en cours...",
|
||||
f"Le syst\u00e8me d'intelligence artificielle ex\u00e9cute la "
|
||||
f"t\u00e2che '{workflow_name}' sur votre \u00e9cran.",
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -459,6 +499,48 @@ class SmartTrayV1:
|
||||
|
||||
threading.Thread(target=_replay, daemon=True).start()
|
||||
|
||||
def _on_emergency_stop(self, _icon=None, _item=None) -> None:
|
||||
"""Arret d'urgence — stoppe TOUTES les activites de l'agent immediatement.
|
||||
|
||||
Controle humain obligatoire (Article 14, Reglement IA).
|
||||
Arrete l'enregistrement, le replay ET le heartbeat d'un seul clic.
|
||||
Toujours accessible dans le menu, quel que soit l'etat de l'agent.
|
||||
"""
|
||||
logger.warning("ARRET D'URGENCE declenche par l'utilisateur")
|
||||
|
||||
# Arreter l'enregistrement si en cours
|
||||
if self._shared_state is not None:
|
||||
if self._shared_state.is_recording:
|
||||
try:
|
||||
self._shared_state.stop_recording()
|
||||
except Exception as e:
|
||||
logger.error("Erreur arret enregistrement d'urgence : %s", e)
|
||||
|
||||
# Arreter le replay si en cours
|
||||
if self._shared_state.is_replay_active:
|
||||
self._shared_state.set_replay_active(False)
|
||||
else:
|
||||
# Fallback sans etat partage
|
||||
if self.is_recording:
|
||||
try:
|
||||
self.on_stop()
|
||||
except Exception as e:
|
||||
logger.error("Erreur arret session d'urgence : %s", e)
|
||||
|
||||
# Forcer l'etat local a l'arret
|
||||
with self._state_lock:
|
||||
self.is_recording = False
|
||||
self.actions_count = 0
|
||||
self._replay_active = False
|
||||
self._update_icon()
|
||||
|
||||
# Notification
|
||||
self._notifier.notify(
|
||||
"\u26d4 Arr\u00eat d'urgence",
|
||||
"Toutes les activit\u00e9s ont \u00e9t\u00e9 arr\u00eat\u00e9es.",
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
def _on_open_folder(self, _icon=None, _item=None) -> None:
|
||||
"""Ouvre le dossier des sessions dans l'explorateur de fichiers."""
|
||||
from ..config import SESSIONS_ROOT
|
||||
@@ -593,7 +675,12 @@ class SmartTrayV1:
|
||||
self._update_icon()
|
||||
|
||||
if active:
|
||||
self._notifier.notify("L\u00e9a", "Je m'en occupe...")
|
||||
# Transparence mode autonome (Article 50, Reglement IA)
|
||||
self._notifier.notify(
|
||||
"L\u00e9a",
|
||||
"Le syst\u00e8me d'intelligence artificielle ex\u00e9cute "
|
||||
"une t\u00e2che sur votre \u00e9cran.",
|
||||
)
|
||||
else:
|
||||
self._notifier.notify("L\u00e9a", "C'est fait !")
|
||||
|
||||
@@ -645,11 +732,8 @@ class SmartTrayV1:
|
||||
|
||||
def run(self) -> None:
|
||||
"""Demarre le tray, les threads de fond, et entre dans la boucle principale."""
|
||||
# Notification d'accueil (avec identifiant machine)
|
||||
self._notifier.notify(
|
||||
"L\u00e9a",
|
||||
f"Bonjour ! L\u00e9a est pr\u00eate.",
|
||||
)
|
||||
# Notification d'accueil — divulgation IA (Article 50, Reglement IA)
|
||||
self._notifier.greet()
|
||||
|
||||
# Enregistrer le hotkey global Ctrl+Shift+L (toggle chat)
|
||||
self._start_hotkey()
|
||||
|
||||
Reference in New Issue
Block a user