# Architecture de Configuration Agent -- Serveur **Date** : 2026-04-13 **Auteur** : Analyse automatique (Claude) **Statut** : Diagnostic + recommandation -- PAS de code modifie --- ## 1. Schema de la chaine de configuration ``` config.txt (fichier plat sur le poste Windows) | | Lea.bat (for /f "eol=# tokens=1,* delims==" => set) v Variables d'environnement du process Python | +---> agent_v1/config.py | SERVER_URL = os.getenv("RPA_SERVER_URL", "http://localhost:5005/api/v1") | STREAMING_ENDPOINT = f"{SERVER_URL}/traces/stream" | API_TOKEN = os.environ.get("RPA_API_TOKEN", "") | +---> lea_ui/server_client.py | _stream_base = RPA_SERVER_URL (si defini) OU http://{RPA_SERVER_HOST}:{5005} | Utilise _stream_base + "/api/v1/traces/stream/..." (chemin COMPLET en dur) | +---> agent_v1/main.py | _background_heartbeat_loop : utilise SERVER_URL DIRECTEMENT (pas STREAMING_ENDPOINT) | _replay_poll_loop : passe SERVER_URL a executor.poll_and_execute() | +---> agent_v1/core/executor.py poll_and_execute : construit f"{server_url}/traces/stream/replay/next" _server_resolve_target : construit f"{server_url}/traces/stream/replay/resolve_target" _observe_screen : construit f"{server_url}/traces/stream/replay/pre_analyze" ``` ### Cote serveur (generation du config.txt) ``` web_dashboard/app.py | +---> _RPA_PUBLIC_URL = RPA_PUBLIC_URL ou RPA_SERVER_URL ou "https://lea.labs.laurinebazin.design" +---> _build_custom_config(machine_id, user_name, token) | server_url = _RPA_PUBLIC_URL.rstrip("/") | if not server_url.endswith("/api/v1"): | server_url += "/api/v1" <=== CORRECTIF RECENT (fonctionne) | -> RPA_SERVER_URL={server_url} | -> RPA_SERVER_HOST={host sans schema ni path} | +---> deploy/lea_package/config.txt (template statique dans le repo) +---> deploy/installer/config_template.txt (template pour installeur Inno Setup) ``` ### Cote serveur (routes FastAPI, port 5005) ``` agent_v0/server_v1/api_stream.py | +---> TOUTES les routes sous /api/v1/traces/stream/... | /register, /event, /image, /finalize, /replay/next, etc. | +---> /health (public, a la racine) | +---> Middleware url_compat_rewrite : /traces/stream/... => /api/v1/traces/stream/... ``` --- ## 2. Inventaire des incoherences ### INC-1 : Deux systemes paralleles de resolution d'URL (CRITIQUE) **Fichiers concernes** : - `agent_v0/agent_v1/config.py` (ligne 43-45) : `SERVER_URL` = URL complete avec `/api/v1` ; `STREAMING_ENDPOINT = SERVER_URL + "/traces/stream"` - `agent_v0/lea_ui/server_client.py` (ligne 77-81) : `_stream_base` = `RPA_SERVER_URL` BRUTE (sans garantie de `/api/v1`) ; utilise ensuite `_stream_base + "/api/v1/traces/stream/..."` (chemin complet en dur) **Probleme** : Si `RPA_SERVER_URL=https://lea.labs.laurinebazin.design/api/v1` : - `config.py` produit `STREAMING_ENDPOINT = .../api/v1/traces/stream` (CORRECT) - `server_client.py` produit `_stream_base/api/v1/traces/stream/...` = `.../api/v1/api/v1/traces/stream/...` (DOUBLE `/api/v1` !) Si `RPA_SERVER_URL=https://lea.labs.laurinebazin.design` : - `config.py` produit `STREAMING_ENDPOINT = .../traces/stream` (MANQUE `/api/v1`) - `server_client.py` produit `_stream_base/api/v1/traces/stream/...` (CORRECT) **Il n'existe aucune valeur de `RPA_SERVER_URL` qui fasse fonctionner les deux modules simultanement.** ### INC-2 : `_background_heartbeat_loop` utilise `SERVER_URL` au lieu de `STREAMING_ENDPOINT` **Fichier** : `agent_v0/agent_v1/main.py` (ligne 370) ```python req.post(f"{SERVER_URL}/traces/stream/image", ...) ``` `SERVER_URL` = `http://localhost:5005/api/v1` => URL finale = `/api/v1/traces/stream/image` (CORRECT). Mais c'est un accident : le heartbeat bypasse `STREAMING_ENDPOINT` (`SERVER_URL + "/traces/stream"`) et reconstruit son propre chemin. Le meme pattern se retrouve dans `executor.py` qui recoit `server_url` (= `SERVER_URL`) et construit `f"{server_url}/traces/stream/replay/next"`. **Consequence** : Deux conventions coexistent : 1. `STREAMING_ENDPOINT + "/register"` (streamer.py) -- attend que `SERVER_URL` contienne `/api/v1` 2. `SERVER_URL + "/traces/stream/image"` (main.py, executor.py) -- attend aussi que `SERVER_URL` contienne `/api/v1` Aujourd'hui les deux marchent parce que `SERVER_URL` inclut `/api/v1`. Mais `server_client.py` utilise une troisieme convention (chemin complet en dur), d'ou l'INC-1. ### INC-3 : `LeaServerClient.check_connection()` appelle `/health` sur `_stream_base` **Fichier** : `agent_v0/lea_ui/server_client.py` (ligne 161) ```python resp = requests.get(f"{self._stream_base}/health", ...) ``` Si `_stream_base = "https://lea.labs.laurinebazin.design/api/v1"`, l'URL finale est `/api/v1/health` -- **cette route n'existe pas**. La route sante est `GET /health` (racine). Si `_stream_base = "https://lea.labs.laurinebazin.design"`, l'URL finale est `/health` -- OK. Encore un conflit : `server_client.py` attend une URL **sans** `/api/v1`, `config.py` la fournit **avec**. ### INC-4 : Template `deploy/lea_package/config.txt` contient de vrais secrets **Fichier** : `deploy/lea_package/config.txt` (ligne 19) ``` RPA_API_TOKEN=86031addb338e449fccdb1a983f61807aec15d42d482b9c7748ad607dc23caab ``` Ce fichier est versionne dans Git. Le token reel est en clair dans le repo. ### INC-5 : Copie ancienne non maintenue (`agent_v0/deploy/windows_client/`) **Fichier** : `agent_v0/deploy/windows_client/config.py` (ligne 41) ```python SERVER_URL = os.getenv("RPA_SERVER_URL", "http://localhost:8000/api/traces/upload") ``` Cette copie pointe encore sur l'ancien serveur API (port 8000, endpoint `/api/traces/upload`). Completement obsolete. Idem pour `agent_v0/config.py` (ligne 41). ### INC-6 : `RPA_SERVER_HOST` sert a deux choses incompatibles - **`server_client.py`** : utilise `RPA_SERVER_HOST` comme **hostname nu** (ex: `192.168.1.40` ou `lea.labs.laurinebazin.design`) pour construire `http://{host}:5005` - **`executor.py`** : utilise `RPA_SERVER_HOST` pour construire des URLs **Ollama** (`http://{host}:11434/api/chat`) - **`main.py`** (ligne 94-95) : passe `RPA_SERVER_HOST` comme `server_host` au `LeaServerClient` et au `ChatWindow` Le `config.txt` genere par Fleet met `RPA_SERVER_HOST=lea.labs.laurinebazin.design`. Cela provoque : - `executor.py` tente `http://lea.labs.laurinebazin.design:11434/api/chat` -- **Ollama n'est pas expose sur Internet** (echec silencieux) ### INC-7 : Redirect POST -> GET (Bug 3, non resolu cote client) La lib Python `requests` suit les redirections 301/302 en transformant les POST en GET (RFC 7231). Quand NPM redirige `http://` vers `https://`, tous les POST streaming (register, event, image, finalize) deviennent des GET et recevront un 405. **Aucune protection cote client**. Le middleware serveur `url_compat_rewrite` ne resout que le probleme de path (pas le probleme de schema HTTP/HTTPS). --- ## 3. Recommandation architecturale ### Principe : une seule variable, deux composants ``` RPA_SERVER_URL = URL complete incluant le prefixe API Exemples : http://localhost:5005/api/v1 (dev local) http://192.168.1.40:5005/api/v1 (LAN) https://lea.labs.laurinebazin.design/api/v1 (Internet) ``` **Toutes les URLs de l'agent sont construites par concatenation de `SERVER_URL` + suffixe de route** : ```python # config.py SERVER_URL = os.getenv("RPA_SERVER_URL", "http://localhost:5005/api/v1") # Pour le health-check (route a la racine, pas sous /api/v1) : SERVER_BASE = SERVER_URL.rsplit("/api/v1", 1)[0] # ex: "https://lea.labs.laurinebazin.design" # streamer.py, executor.py, main.py : tous utilisent STREAMING_ENDPOINT = f"{SERVER_URL}/traces/stream" # inchange HEALTH_ENDPOINT = f"{SERVER_BASE}/health" # NOUVEAU # server_client.py : supprimer le systeme parallele # _stream_base = SERVER_URL (plus de re-concatenation de "/api/v1" en dur) ``` ### Supprimer `RPA_SERVER_HOST` Cette variable est source de confusion (INC-6). Elle ne doit pas exister. Le hostname est derive de `RPA_SERVER_URL` si besoin (pour l'affichage dans la chat window, etc.). L'acces Ollama doit avoir sa propre variable `RPA_OLLAMA_HOST` (defaut : `localhost`), car Ollama n'est JAMAIS accessible via le reverse proxy Internet. ### Protection POST -> GET Ajouter dans `streamer.py` et `executor.py` : ```python # Dans chaque session requests : session = requests.Session() session.max_redirects = 0 # Refuser les redirections (echouer bruyamment) ``` Ou forcer `https://` cote client si le host est un domaine public (pas localhost/IP privee). ### Template config.txt ``` RPA_SERVER_URL=CONFIGURE_ME RPA_API_TOKEN=CONFIGURE_ME RPA_MACHINE_ID=CONFIGURE_ME ``` Pas de token reel, pas de valeur par defaut fonctionnelle. L'agent doit refuser de demarrer si `RPA_SERVER_URL` contient "CONFIGURE_ME". --- ## 4. Matrice scenarios x configuration | Scenario | RPA_SERVER_URL | RPA_API_TOKEN | Notes | |---|---|---|---| | Dev local (meme machine) | `http://localhost:5005/api/v1` | (vide ou token dev) | RPA_AUTH_DISABLED=true cote serveur | | LAN interne (Dom <-> VM) | `http://192.168.1.40:5005/api/v1` | token prod | HTTP OK en LAN ferme | | Internet via NPM (TIM) | `https://lea.labs.laurinebazin.design/api/v1` | token prod | HTTPS obligatoire, pas de redirect | | Futur DGX on-premise | `http://:5005/api/v1` ou `https://...` | token prod | Selon reseau client | --- ## 5. Liste des fichiers a corriger ### Priorite HAUTE (bloquant pour le deploiement TIM) | Fichier | Ligne(s) | Action | |---|---|---| | `agent_v0/lea_ui/server_client.py` | 77-81, 161, 230, 287, 321, 349 | Supprimer la double logique `_stream_base`. Utiliser `SERVER_URL` de config.py comme base, ne plus concatener `/api/v1` en dur dans les appels. Pour `/health`, utiliser `SERVER_BASE` (sans `/api/v1`). | | `agent_v0/agent_v1/main.py` | 94-95 | Supprimer l'utilisation de `RPA_SERVER_HOST` pour construire le `LeaServerClient`. Passer `SERVER_URL` directement. | | `agent_v0/agent_v1/main.py` | 370 | Utiliser `STREAMING_ENDPOINT` au lieu de reconstruire le chemin manuellement. | | `agent_v0/agent_v1/network/streamer.py` | 34 | Aucun changement (utilise deja `STREAMING_ENDPOINT` correctement). | | `deploy/lea_package/config.txt` | 14, 19, 20 | Remplacer les valeurs par des placeholders `CONFIGURE_ME`. Supprimer le token reel. | | `deploy/installer/config_template.txt` | 26-27 | Idem, remplacer le token reel par un placeholder. | ### Priorite MOYENNE (coherence du codebase) | Fichier | Ligne(s) | Action | |---|---|---| | `agent_v0/agent_v1/config.py` | 43-45 | Ajouter `SERVER_BASE` (URL sans `/api/v1`) pour le health-check. | | `agent_v0/agent_v1/core/executor.py` | 1144, 1280, 1595 | Remplacer `RPA_SERVER_HOST` par une nouvelle var `RPA_OLLAMA_HOST` (defaut `localhost`). | | `web_dashboard/app.py` | 2055-2060 | Renommer `_RPA_PUBLIC_URL` en `_RPA_PUBLIC_SERVER_URL`. S'assurer que le `/api/v1` est toujours present dans le config.txt genere (deja fait, ligne 2097-2098). | | `web_dashboard/app.py` | 2119 | Supprimer la ligne `RPA_SERVER_HOST=` du config.txt genere. | ### Priorite BASSE (nettoyage) | Fichier | Action | |---|---| | `agent_v0/config.py` | Supprimer ou marquer deprecated (ancien agent V0, port 8000). | | `agent_v0/deploy/windows_client/` | Supprimer l'arborescence entiere (copie obsolete, remplacee par le ZIP Fleet). | | `agent_v0/deploy/windows_client/config.py` | Port 8000, endpoint `/api/traces/upload` -- completement mort. | ### Protection anti-redirect (Bug 3) | Fichier | Action | |---|---| | `agent_v0/agent_v1/network/streamer.py` | Utiliser `requests.Session()` avec `max_redirects=0` ou forcer HTTPS si domaine public. | | `agent_v0/agent_v1/core/executor.py` | Idem pour les appels HTTP du replay (resolve_target, pre_analyze, replay/next, replay/result). | --- ## 6. Diagramme de flux (etat cible) ``` config.txt (genere par Fleet ou rempli a la main) | | RPA_SERVER_URL=https://lea.labs.laurinebazin.design/api/v1 | RPA_API_TOKEN= | RPA_MACHINE_ID= | (plus de RPA_SERVER_HOST) | v Lea.bat -> set variables d'environnement | v agent_v1/config.py | SERVER_URL = "https://lea.labs.laurinebazin.design/api/v1" | SERVER_BASE = "https://lea.labs.laurinebazin.design" | STREAMING_ENDPOINT = "https://lea.labs.laurinebazin.design/api/v1/traces/stream" | HEALTH_ENDPOINT = "https://lea.labs.laurinebazin.design/health" | +-------> streamer.py : STREAMING_ENDPOINT + "/register", "/event", "/image", "/finalize" +-------> main.py : STREAMING_ENDPOINT + "/image" (heartbeat) | SERVER_URL (passe a executor.poll_and_execute) +-------> executor.py : SERVER_URL + "/traces/stream/replay/next", etc. +-------> server_client.py : SERVER_URL + "/traces/stream/workflows", etc. | HEALTH_ENDPOINT (pour check_connection) | v (HTTPS, port 443) NPM Reverse Proxy | v (HTTP, port 5005) FastAPI api_stream.py | /health | /api/v1/traces/stream/register | /api/v1/traces/stream/event | /api/v1/traces/stream/image | /api/v1/traces/stream/replay/next | ... ``` --- ## 7. Resume des bugs originaux et leur resolution | Bug | Cause racine | Correction | |---|---|---| | Bug 1 (URL obsolete dans config.txt) | Template `deploy/lea_package/config.txt` jamais mis a jour | DEJA CORRIGE (le fichier actuel contient `/api/v1`). Mais le token reel est toujours en clair. | | Bug 2 (mismatch /api/v1) | Deux modules (`config.py` vs `server_client.py`) avec des conventions incompatibles pour construire les URLs | Unifier sur une seule convention : `RPA_SERVER_URL` inclut TOUJOURS `/api/v1`. `server_client.py` doit utiliser `SERVER_URL` de `config.py` au lieu de reimplementer sa propre logique. | | Bug 3 (POST -> GET sur redirect) | `requests` suit les 301 en changeant la methode HTTP | Forcer HTTPS cote client quand le domaine est public, OU desactiver les redirections (`max_redirects=0`) pour echouer explicitement. Le middleware serveur `url_compat_rewrite` est un filet de securite pour le path, pas pour le schema. |