feat: replay E2E fonctionnel — 25/25 actions, 0 retries, SomEngine via serveur

Validé sur PC Windows (DESKTOP-58D5CAC, 2560x1600) :
- 8 clics résolus visuellement (1 anchor_template, 1 som_text_match, 6 som_vlm)
- Score moyen 0.75, temps moyen 1.6s
- Texte tapé correctement (bonjour, test word, date, email)
- 0 retries, 2 actions non vérifiées (OK)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-03-31 14:04:41 +02:00
parent 5e0b53cfd1
commit a7de6a488b
79542 changed files with 6091757 additions and 1 deletions

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "agent_v0"))
from user_config import load_user_config
import json
print("=== Debug agent config ===")
# Load config
config = load_user_config()
print(f"Config loaded: {config}")
# Check specific keys
print(f"enable_encryption: {config.get('enable_encryption')}")
print(f"encryption_password: {config.get('encryption_password')}")
# Check config file directly
with open("agent_config.json", 'r') as f:
direct_config = json.load(f)
print(f"Direct config file: {direct_config}")

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
import sys
sys.path.insert(0, '.')
# Test d'exécution ligne par ligne
try:
print("1. Importing modules...")
import logging
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Dict, List, Any
from core.system.models import SimpleFailureEvent
print("✓ All imports successful")
print("2. Creating logger...")
logger = logging.getLogger(__name__)
print("✓ Logger created")
print("3. Defining SimpleFailureWindow...")
class SimpleFailureWindow:
def __init__(self, window_start: datetime, window_duration_s: int, failures: List = None):
self.window_start = window_start
self.window_duration_s = window_duration_s
self.failures = failures or []
def add_failure(self, failure: SimpleFailureEvent) -> None:
self.failures.append(failure)
self.cleanup_expired()
def get_failure_count(self) -> int:
self.cleanup_expired()
return len(self.failures)
def cleanup_expired(self) -> None:
now = datetime.now()
cutoff = now - timedelta(seconds=self.window_duration_s)
self.failures = [f for f in self.failures if f.timestamp >= cutoff]
print("✓ SimpleFailureWindow defined")
print("4. Defining CircuitBreaker...")
class CircuitBreaker:
def __init__(self, policy: Dict[str, Any]):
self.policy = policy
self.step_consecutive_failures: Dict[str, List] = defaultdict(list)
self.workflow_windows: Dict[str, SimpleFailureWindow] = {}
self.global_window = SimpleFailureWindow(
window_start=datetime.now(),
window_duration_s=policy.get('workflow_fail_window_s', 600),
failures=[]
)
self.step_success_counts: Dict[str, int] = defaultdict(int)
logger.info("CircuitBreaker initialized")
def record_failure(self, workflow_id: str, step_id: str, failure_type: str) -> None:
now = datetime.now()
step_key = f"{workflow_id}:{step_id}"
failure_event = SimpleFailureEvent(
timestamp=now,
workflow_id=workflow_id,
step_id=step_id,
failure_type=failure_type
)
self.step_consecutive_failures[step_key].append(failure_event)
self.step_success_counts[step_key] = 0
if workflow_id not in self.workflow_windows:
self.workflow_windows[workflow_id] = SimpleFailureWindow(
window_start=now,
window_duration_s=self.policy.get('workflow_fail_window_s', 600),
failures=[]
)
self.workflow_windows[workflow_id].add_failure(failure_event)
self.global_window.add_failure(failure_event)
print("✓ CircuitBreaker defined")
print("5. Testing CircuitBreaker...")
cb = CircuitBreaker({'test': True})
print("✓ CircuitBreaker instance created")
print("Available methods:", [method for method in dir(cb) if not method.startswith('_')])
print("Has record_failure:", hasattr(cb, 'record_failure'))
if hasattr(cb, 'record_failure'):
print("6. Testing record_failure...")
cb.record_failure("test_workflow", "test_step", "TEST_ERROR")
print("✓ record_failure works")
except Exception as e:
print(f"✗ Error: {e}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1,56 @@
exc()t_back.prinrace ttraceback
import e}")
ur: { Erre" print(f:
tion as ept Excep
exce_1'])}")low:step'test_workfs[ive_failuresecutp_con(cb.steenutifs: {lonséc"📊 Échecs c print(f
)
gistré"chec enret(" É
prin")T_NOT_FOUNDRGE, "TA"tep_1", "s_workflowestfailure("t.record_ cbple...")
n échec simest d'ut("\n🧪 T
prin QUANT")
r}: MAN {att nt(f" pri else:
e}")
alu)} = {vvaluee(r}: {typ"{att print(f , attr)
(cbattr value = get :
attr)cb, tr( hasat if s:
ed_attrequirn r for attr i ]
w'
indo 'global_w
_windows',workflow ' ,
_counts'cesstep_suc 's',
ailurestive_fep_consecu 'st
policy', ' [
attrs =equired_ r)
is:"s requ attributcation desfi"\n🔍 Vériint(
pr")
ttr}t(f" - {a prin '):
ith('_rtsw attr.sta if notb):
ir(cin d for attr s:")
s disponible🔍 Attributprint("
éé")aker crrcuitBre" Ci
print(er(policy)akrercuitB
cb = Ci.")r..reakecuitBion du Cirréatnt("📋 C pri}
shold': 2
_threess_reset 'succ,
_window': 30_inl_maxglobal_fai ': 10,
w'ndol_max_in_wifailow_orkf'w
600,dow_s':w_fail_winworkflo 'ed': 3,
ak_to_degradrestl_'step_fai{
y = polic
ssi")er réutBreakt Circuior Impint(" prer
reaktBort Circuier imprcuit_breakre.system.cifrom cotry:
')
pend('.
sys.path.aprt sys"
impo
""rcuitBreake pour Cirbug simple"
De""ython3
n/env psr/bi#!/u

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Debug script to analyze the encryption/decryption issue between agent and server.
"""
import os
import sys
from pathlib import Path
# Add paths for imports
sys.path.insert(0, str(Path(__file__).parent / "agent_v0"))
sys.path.insert(0, str(Path(__file__).parent / "server"))
def debug_encryption_issue():
"""Debug the encryption/decryption mismatch."""
print("=== Debugging Encryption Issue ===")
# Load environment
env_local_path = Path(".env.local")
if env_local_path.exists():
with open(env_local_path, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ[key.strip()] = value.strip()
password = os.getenv("ENCRYPTION_PASSWORD")
print(f"Using password: {password[:16]}...")
# Find the most recent .enc file
agent_sessions_dir = Path("agent_v0/sessions")
enc_files = list(agent_sessions_dir.glob("*.enc"))
if not enc_files:
print("❌ No .enc files found in agent_v0/sessions/")
return False
# Get the most recent .enc file
latest_enc = max(enc_files, key=lambda p: p.stat().st_mtime)
print(f"Testing with file: {latest_enc}")
print(f"File size: {latest_enc.stat().st_size} bytes")
# Test with agent's decryption function
print("\n--- Testing Agent's Decryption ---")
try:
from storage_encrypted import decrypt_session_file as agent_decrypt
agent_result = agent_decrypt(str(latest_enc), password)
print(f"✅ Agent decryption successful: {agent_result}")
# Check if it's a valid ZIP
import zipfile
with zipfile.ZipFile(agent_result, 'r') as zf:
files = zf.namelist()
print(f" ZIP contains {len(files)} files")
except Exception as e:
print(f"❌ Agent decryption failed: {e}")
return False
# Test with server's decryption function
print("\n--- Testing Server's Decryption ---")
try:
# Import server's version
import importlib.util
spec = importlib.util.spec_from_file_location("server_storage", "server/storage_encrypted.py")
server_storage = importlib.util.module_from_spec(spec)
spec.loader.exec_module(server_storage)
server_result = server_storage.decrypt_session_file(str(latest_enc), password)
print(f"✅ Server decryption successful: {server_result}")
# Check if it's a valid ZIP
with zipfile.ZipFile(server_result, 'r') as zf:
files = zf.namelist()
print(f" ZIP contains {len(files)} files")
except Exception as e:
print(f"❌ Server decryption failed: {e}")
print(f" Error type: {type(e).__name__}")
print(f" Error details: {str(e)}")
# Let's analyze the file structure
print("\n--- Analyzing File Structure ---")
try:
with open(latest_enc, 'rb') as f:
salt = f.read(16)
iv = f.read(16)
ciphertext = f.read()
print(f"Salt length: {len(salt)} bytes")
print(f"IV length: {len(iv)} bytes")
print(f"Ciphertext length: {len(ciphertext)} bytes")
print(f"Total file size: {16 + 16 + len(ciphertext)} bytes")
if len(ciphertext) % 16 != 0:
print(f"⚠️ Ciphertext length not multiple of 16: {len(ciphertext) % 16} remainder")
else:
print("✅ Ciphertext length is multiple of 16")
except Exception as e2:
print(f"❌ Error analyzing file: {e2}")
return False
print("\n✅ Both agent and server decryption work!")
return True
if __name__ == "__main__":
success = debug_encryption_issue()
sys.exit(0 if success else 1)

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""
Simple test to debug the encryption issue.
"""
import os
import sys
from pathlib import Path
# Load environment
env_local_path = Path(".env.local")
if env_local_path.exists():
print(f"Loading environment from {env_local_path}")
with open(env_local_path, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ[key.strip()] = value.strip()
print("Environment loaded:")
print(f"ENCRYPTION_PASSWORD: {os.getenv('ENCRYPTION_PASSWORD', 'NOT_SET')[:20]}...")
print(f"RPA_SERVER_URL: {os.getenv('RPA_SERVER_URL', 'NOT_SET')}")
print(f"RPA_AUTH_DISABLED: {os.getenv('RPA_AUTH_DISABLED', 'NOT_SET')}")
# Add paths
sys.path.insert(0, str(Path(__file__).parent / "agent_v0"))
try:
from user_config import load_user_config
config = load_user_config()
print(f"\nAgent config:")
print(f" encryption_password: {config.get('encryption_password')}")
print(f" enable_encryption: {config.get('enable_encryption')}")
# This is the key issue - when encryption_password is None,
# the agent uses session_id as password
if config.get('encryption_password') is None:
print(" -> Agent will use session-based password (rpa_vision_v3_<session_id>)")
server_password = os.getenv("ENCRYPTION_PASSWORD", "rpa_vision_v3_default_key")
agent_password_type = "session-based" if config.get('encryption_password') is None else "fixed"
print(f"\nPassword mismatch:")
print(f" Agent uses: {agent_password_type} password")
print(f" Server uses: fixed password from ENCRYPTION_PASSWORD")
print(f" This causes 'Padding invalide' error when server tries to decrypt")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""
Script pour tester la fonction _is_public
"""
import sys
from pathlib import Path
# Ajouter le répertoire parent au path
sys.path.insert(0, str(Path(__file__).parent))
from core.security.fastapi_security import DEFAULT_PUBLIC_PATHS
def test_is_public():
print("=== Test des endpoints publics ===")
print(f"DEFAULT_PUBLIC_PATHS: {DEFAULT_PUBLIC_PATHS}")
test_paths = [
"/healthz",
"/api/traces/debug-auth",
"/api/traces/debug-env",
"/api/traces/upload",
"/metrics",
"/",
]
for path in test_paths:
is_public = path in DEFAULT_PUBLIC_PATHS
print(f"{path}: {'✅ PUBLIC' if is_public else '❌ PRIVÉ'}")
if __name__ == "__main__":
test_is_public()

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
Script de debug pour tester l'authentification du serveur
"""
import os
import sys
from pathlib import Path
# Ajouter le répertoire parent au path
sys.path.insert(0, str(Path(__file__).parent))
from core.security.api_tokens import TokenManager, validate_token
def main():
print("=== Debug Authentification Serveur ===")
# Vérifier les variables d'environnement
print("\n1. Variables d'environnement:")
admin_token = os.getenv('RPA_TOKEN_ADMIN')
readonly_token = os.getenv('RPA_TOKEN_READONLY')
print(f"RPA_TOKEN_ADMIN: {admin_token[:8] + '...' if admin_token else 'NON DÉFINI'}")
print(f"RPA_TOKEN_READONLY: {readonly_token[:8] + '...' if readonly_token else 'NON DÉFINI'}")
# Tester le TokenManager
print("\n2. Test TokenManager:")
try:
tm = TokenManager()
print(f"Admin tokens: {len(tm.admin_tokens)}")
print(f"Read-only tokens: {len(tm.read_only_tokens)}")
# Tester la validation du token admin
if admin_token:
print(f"\n3. Test validation token admin:")
try:
token_info = tm.validate_token(admin_token)
print(f"✅ Token admin valide: {token_info.role}")
except Exception as e:
print(f"❌ Token admin invalide: {e}")
# Tester la validation du token readonly
if readonly_token:
print(f"\n4. Test validation token readonly:")
try:
token_info = tm.validate_token(readonly_token)
print(f"✅ Token readonly valide: {token_info.role}")
except Exception as e:
print(f"❌ Token readonly invalide: {e}")
except Exception as e:
print(f"❌ Erreur TokenManager: {e}")
# Tester la fonction validate_token directement
print(f"\n5. Test fonction validate_token directe:")
if admin_token:
try:
token_info = validate_token(admin_token)
print(f"✅ validate_token réussie: {token_info.role}")
except Exception as e:
print(f"❌ validate_token échouée: {e}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""
Debug simple pour tester l'import du module de déchiffrement côté serveur.
"""
import sys
from pathlib import Path
# Ajouter le répertoire parent au path comme le fait le serveur
sys.path.insert(0, str(Path(__file__).parent))
print("=== Debug import serveur ===")
print(f"Python path: {sys.path[:3]}")
try:
from agent_v0.storage_encrypted import decrypt_session_file
print("✅ Import agent_v0.storage_encrypted réussi")
print(f" Fonction decrypt_session_file: {decrypt_session_file}")
except ImportError as e:
print(f"❌ Erreur import agent_v0.storage_encrypted: {e}")
try:
from storage_encrypted import decrypt_session_file
print("✅ Import storage_encrypted réussi")
except ImportError as e:
print(f"❌ Erreur import storage_encrypted: {e}")

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""
Script simple pour tester les paths publics
"""
DEFAULT_PUBLIC_PATHS = {
"/healthz",
"/metrics",
"/",
"/docs",
"/redoc",
"/openapi.json",
"/api/traces/debug-auth", # Debug endpoint
"/api/traces/debug-env", # Debug endpoint
}
def test_is_public():
print("=== Test des endpoints publics ===")
print(f"DEFAULT_PUBLIC_PATHS: {DEFAULT_PUBLIC_PATHS}")
test_paths = [
"/healthz",
"/api/traces/debug-auth",
"/api/traces/debug-env",
"/api/traces/upload",
"/metrics",
"/",
]
for path in test_paths:
is_public = path in DEFAULT_PUBLIC_PATHS
print(f"{path}: {'✅ PUBLIC' if is_public else '❌ PRIVÉ'}")
if __name__ == "__main__":
test_is_public()