chore: ajouter agent_v0/ au tracking git (était un repo embarqué)

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>
This commit is contained in:
Dom
2026-03-18 11:12:23 +01:00
parent af83552923
commit ae65be2555
82 changed files with 15616 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
# server_v1/vm_controller.py
"""
Contrôleur de VM Windows via libvirt (virsh).
Injection d'événements HID (souris/clavier) au niveau de l'hyperviseur.
C'est le "bras armé" du Stagiaire pour l'exécution GHOST (sans agent).
"""
import subprocess
import logging
import time
logger = logging.getLogger("vm_controller")
class VMController:
def __init__(self, domain_name: str):
self.domain_name = domain_name
def start_vm(self):
"""Démarre la VM si elle est éteinte."""
try:
logger.info(f"🚀 Démarrage de la VM {self.domain_name}...")
subprocess.run(f"virsh start {self.domain_name}", shell=True, check=True)
return True
except Exception as e:
logger.error(f"❌ Impossible de démarrer la VM: {e}")
return False
def stop_vm(self, force=False):
"""Arrête la VM proprement (ou force l'arrêt)."""
cmd = "destroy" if force else "shutdown"
try:
logger.info(f"🛑 Arrêt de la VM {self.domain_name} ({cmd})...")
subprocess.run(f"virsh {cmd} {self.domain_name}", shell=True, check=True)
return True
except Exception as e:
logger.error(f"❌ Erreur lors de l'arrêt: {e}")
return False
def get_status(self) -> str:
"""Retourne l'état actuel de la VM (running, shut off, etc.)."""
try:
res = subprocess.check_output(f"virsh domstate {self.domain_name}", shell=True)
return res.decode().strip()
except:
return "unknown"
def create_checkpoint(self, checkpoint_name: str = "before_workflow"):
"""Crée un snapshot de la VM pour pouvoir revenir en arrière en cas d'erreur."""
try:
logger.info(f"📸 Création du checkpoint '{checkpoint_name}' pour {self.domain_name}...")
# On utilise --atomic pour garantir l'intégrité
subprocess.run(f"virsh snapshot-create-as {self.domain_name} {checkpoint_name} --atomic", shell=True, check=True)
return True
except Exception as e:
logger.error(f"❌ Échec de création du checkpoint: {e}")
return False
def restore_checkpoint(self, checkpoint_name: str = "before_workflow"):
"""Restaure la VM à un état précédent instantanément."""
try:
logger.warning(f"🔄 Restauration du checkpoint '{checkpoint_name}' pour {self.domain_name}...")
# On force la restauration
subprocess.run(f"virsh snapshot-revert {self.domain_name} {checkpoint_name} --force", shell=True, check=True)
return True
except Exception as e:
logger.error(f"❌ Échec de la restauration: {e}")
return False
def inject_click(self, x_pct: float, y_pct: float, button: str = "left"):
"""
Injecte un clic de souris aux coordonnées proportionnelles (0.0-1.0).
Utilise l'interface QEMU via virsh pour la précision absolue.
"""
try:
# Note: Pour QEMU/KVM, on utilise souvent l'interface moniteur 'qemu-monitor-command'
# pour envoyer des coordonnées absolues si une tablette USB est présente (évite le drift).
# Exemple de commande QEMU Monitor pour un clic absolu
# (nécessite que la VM ait un périphérique tablette USB configuré)
cmd = f"virsh qemu-monitor-command {self.domain_name} --hmp 'mouse_set 0 0 0 {x_pct} {y_pct}'"
subprocess.run(cmd, shell=True, check=True)
# Simulation du clic bouton
click_cmd = f"virsh qemu-monitor-command {self.domain_name} --hmp 'mouse_button 1'" # 1 = Left
subprocess.run(click_cmd, shell=True, check=True)
time.sleep(0.05)
release_cmd = f"virsh qemu-monitor-command {self.domain_name} --hmp 'mouse_button 0'"
subprocess.run(release_cmd, shell=True, check=True)
logger.info(f"🖱️ Clic GHOST injecté dans {self.domain_name} à ({x_pct}, {y_pct})")
except Exception as e:
logger.error(f"❌ Erreur Injection Clic: {e}")
def inject_text(self, text: str):
"""
Injecte du texte dans la VM en traduisant les caractères en séquences de touches.
Gère les majuscules (via Shift) et les caractères standards.
"""
try:
logger.info(f"⌨️ Saisie GHOST dans {self.domain_name} : '{text}'")
for char in text:
self._send_char(char)
# Petit délai pour simuler une frappe humaine et éviter la saturation du buffer
time.sleep(0.02)
except Exception as e:
logger.error(f"❌ Erreur Injection Texte: {e}")
def inject_key_combo(self, keys: list):
"""
Exécute une combinaison de touches (ex: ['ctrl', 'alt', 'delete']).
"""
try:
combo = "+".join(keys)
cmd = f"virsh sendkey {self.domain_name} {combo}"
subprocess.run(cmd, shell=True, check=True)
logger.info(f"⌨️ Combo GHOST : {combo}")
except Exception as e:
logger.error(f"❌ Erreur Combo: {e}")
def _send_char(self, char: str):
"""Traduit un caractère unique en commande virsh sendkey."""
# Mapping des caractères spéciaux pour virsh
special_map = {
" ": "space", "\n": "enter", "\t": "tab", ".": "dot",
",": "comma", "-": "minus", "_": "underscore", "/": "slash"
}
if char in special_map:
key = special_map[char]
cmd = f"virsh sendkey {self.domain_name} {key}"
elif char.isupper():
key = char.lower()
cmd = f"virsh sendkey {self.domain_name} shift+{key}"
else:
key = char
cmd = f"virsh sendkey {self.domain_name} {key}"
subprocess.run(cmd, shell=True, check=True)
if __name__ == "__main__":
# Test rapide sur une VM de démo
controller = VMController("win10_demo")
# controller.inject_click(0.5, 0.5) # Clic au centre