fix(infra): durcissement headless — pyautogui robuste + cleanup .service

Suite à la mise à jour système qui a basculé Dom de Xorg vers Wayland,
les 4 services systemd côté serveur partaient en boucle restart :
pyautogui levait DisplayConnectionError / KeyError(DISPLAY) à l'import
dans 3 modules, mais l'except n'attrapait qu'ImportError → crash fatal.

Le contournement « ajouter DISPLAY=:1 + XAUTHORITY=/run/user/1000/gdm/
Xauthority dans .service » introduit fin avril était fragile : chemin
invalide en Wayland (Mutter utilise un Xauthority à suffixe aléatoire
qui change à chaque login). Le bon fix est de rendre les imports
pyautogui robustes — le serveur n'a aucun usage légitime de pyautogui,
c'est le client Agent V1 Windows qui pilote souris/clavier.

Modifications :

1. Élargi `except ImportError` → `except Exception` pour pyautogui :
   - agent_chat/autonomous_planner.py
   - core/execution/input_handler.py
   - core/execution/observe_reason_act.py
   (action_executor.py était déjà robuste avec except Exception.)

2. Retiré DISPLAY/XAUTHORITY des 4 .service (rustines) :
   - rpa-streaming.service
   - rpa-vision-v3-{api,worker,dashboard}.service
   Block grounding (RPA_GROUNDING_SOCKET) préservé (initiative
   séparée de partage VRAM, in-flight).

PYAUTOGUI_AVAILABLE=False est désormais attendu côté serveur Linux ;
les chemins aval (action_executor, autonomous_planner) gèrent déjà
ce cas via des branches "actions simulées" / "pyautogui non disponible".

Prépare la roadmap autonome (Léa daemon Linux + VM Windows) qui
tournera headless via systemd au boot, sans dépendre d'aucune
session graphique active.

Tests : 27/27 baseline sprint QW verts.
This commit is contained in:
Dom
2026-05-06 17:19:18 +02:00
parent 87dbe8c5ff
commit d1ebf62217
7 changed files with 37 additions and 3 deletions

View File

@@ -49,7 +49,10 @@ try:
from PIL import Image as PILImage
import pyautogui
PYAUTOGUI_AVAILABLE = True
except ImportError:
except Exception:
# pyautogui peut lever Xlib.error.DisplayConnectionError (pas un ImportError)
# quand X n'est pas accessible — typique d'un service systemd headless côté
# serveur. Le serveur n'a pas besoin de pyautogui (utilisé côté client agent).
PYAUTOGUI_AVAILABLE = False
PILImage = None
pyautogui = None

View File

@@ -19,7 +19,9 @@ logger = logging.getLogger(__name__)
try:
import pyautogui
PYAUTOGUI_AVAILABLE = True
except ImportError:
except Exception:
# pyautogui peut lever Xlib.error.DisplayConnectionError (pas un ImportError)
# quand X n'est pas accessible — typique d'un service systemd côté serveur.
PYAUTOGUI_AVAILABLE = False
try:

View File

@@ -58,7 +58,9 @@ except ImportError:
try:
import pyautogui
PYAUTOGUI_AVAILABLE = True
except ImportError:
except Exception:
# pyautogui peut lever Xlib.error.DisplayConnectionError ou KeyError('DISPLAY')
# quand X n'est pas accessible — typique d'un service systemd côté serveur.
pyautogui = None
PYAUTOGUI_AVAILABLE = False

View File

@@ -14,6 +14,9 @@ WorkingDirectory=/home/dom/ai/rpa_vision_v3
EnvironmentFile=/home/dom/ai/rpa_vision_v3/.env.local
Environment="PYTHONUNBUFFERED=1"
Environment="RPA_SERVICE_NAME=rpa-streaming"
# Service grounding persistant — socket + répertoire d'images partagés via /run/rpa/.
Environment="RPA_GROUNDING_SOCKET=/run/rpa/grounding.sock"
Environment="RPA_GROUNDING_IMG_DIR=/run/rpa"
# Lancement via le module Python (même commande que svc.sh)
ExecStart=/home/dom/ai/rpa_vision_v3/.venv/bin/python3 -m agent_v0.server_v1.api_stream
@@ -29,6 +32,10 @@ KillSignal=SIGTERM
# ---- Hardening (raisonnable pour un poste de dev/prod) ----
NoNewPrivileges=true
PrivateTmp=true
# /run/rpa/ partagé avec rpa-grounding (socket + images)
RuntimeDirectory=rpa
RuntimeDirectoryMode=0755
RuntimeDirectoryPreserve=yes
# Logs -> journald
StandardOutput=journal

View File

@@ -14,6 +14,11 @@ EnvironmentFile=/home/dom/ai/rpa_vision_v3/.env.local
Environment="PYTHONUNBUFFERED=1"
Environment="ENVIRONMENT=production"
Environment="RPA_SERVICE_NAME=rpa-vision-v3-api"
# Service grounding persistant — socket + répertoire d'images partagés via /run/rpa/.
# Si le service rpa-grounding n'est pas démarré, le client retombe automatiquement
# sur le subprocess one-shot (cf. ui_tars_grounder.py).
Environment="RPA_GROUNDING_SOCKET=/run/rpa/grounding.sock"
Environment="RPA_GROUNDING_IMG_DIR=/run/rpa"
ExecStart=/home/dom/ai/rpa_vision_v3/.venv/bin/python3 server/api_upload.py
@@ -25,6 +30,11 @@ TimeoutStopSec=30
# ---- Hardening ----
NoNewPrivileges=true
PrivateTmp=true
# /run/rpa/ partagé avec rpa-grounding pour le socket et les images grounding.
# Le service rpa-grounding crée le répertoire ; ici on l'expose au /run du service.
RuntimeDirectory=rpa
RuntimeDirectoryMode=0755
RuntimeDirectoryPreserve=yes
# Logs -> journald
StandardOutput=journal

View File

@@ -12,6 +12,9 @@ EnvironmentFile=/home/dom/ai/rpa_vision_v3/.env.local
Environment="PYTHONUNBUFFERED=1"
Environment="ENVIRONMENT=production"
Environment="RPA_SERVICE_NAME=rpa-vision-v3-dashboard"
# Service grounding persistant
Environment="RPA_GROUNDING_SOCKET=/run/rpa/grounding.sock"
Environment="RPA_GROUNDING_IMG_DIR=/run/rpa"
ExecStart=/home/dom/ai/rpa_vision_v3/.venv/bin/python3 web_dashboard/app.py
Restart=on-failure

View File

@@ -10,6 +10,9 @@ Group=dom
WorkingDirectory=/home/dom/ai/rpa_vision_v3
EnvironmentFile=/home/dom/ai/rpa_vision_v3/.env.local
Environment="PYTHONUNBUFFERED=1"
# Service grounding persistant — socket + répertoire d'images partagés via /run/rpa/.
Environment="RPA_GROUNDING_SOCKET=/run/rpa/grounding.sock"
Environment="RPA_GROUNDING_IMG_DIR=/run/rpa"
ExecStart=/home/dom/ai/rpa_vision_v3/.venv/bin/python3 server/worker_daemon.py
Restart=on-failure
@@ -18,6 +21,10 @@ TimeoutStopSec=60
NoNewPrivileges=true
PrivateTmp=true
# /run/rpa/ partagé avec rpa-grounding (socket + images)
RuntimeDirectory=rpa
RuntimeDirectoryMode=0755
RuntimeDirectoryPreserve=yes
StandardOutput=journal
StandardError=journal