backup: snapshot post-démo GHT 2026-05-19
Backup état complet après enregistrement vidéo démo de bout en bout. À utiliser comme point de référence pour la consolidation post-démo. Changements majeurs de la session 18-19 mai : - AIVA-URGENCE : page autonome avec preset URL + auto-focus chain - Workflow Demo_urgence_3_db : merge linux_db + steps AIVA + pause humaine NoMachine - Bypass LLM (static_result / static_text) dans replay_engine pour démos déterministes sans appel Ollama - Fix api_stream:3013 — replay_paused au premier polling /next - dag_execute : lift duration_ms vers top-level pour wait runtime - NPM bypass auth /aiva-urgence/ via location ^~ (proxy_host/10.conf hors git) - scripts/cancel-replays.sh — workaround Stop VWB qui ne purge pas la queue Anchors visuels (468) forcés dans le commit pour garantir restorabilité. DB workflows actuelle + ~12 .bak DB de la journée incluses. Sujets identifiés pour consolidation post-démo (TODO) : 1. Bug VWB recapture anchor ne régénère pas le PNG 2. Léa client accumule état mémoire (restart périodique requis) 3. Stop VWB ne purge pas la queue serveur (lien manquant vers /replay/cancel) 4. Bug coord client mss tronqué 2560x60 → mapping Y cassé 5. delay_before/delay_after ignorés au runtime (fix partiel duration_ms) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -227,6 +227,9 @@ from .replay_engine import (
|
||||
_handle_extract_text_action,
|
||||
_handle_extract_table_action,
|
||||
_handle_t2a_decision_action,
|
||||
_handle_llm_generate_action,
|
||||
_handle_concat_text_vars_action,
|
||||
_handle_paste_and_execute_action,
|
||||
_expand_compound_steps,
|
||||
_pre_check_screen_state as _pre_check_screen_state_impl,
|
||||
_detect_popup_hint as _detect_popup_hint_impl,
|
||||
@@ -2946,7 +2949,8 @@ async def get_next_action(session_id: str, machine_id: str = "default"):
|
||||
if not queue:
|
||||
return {"action": None, "session_id": session_id, "machine_id": machine_id}
|
||||
|
||||
# ── Boucle de traitement : actions serveur (extract_text, t2a_decision)
|
||||
# ── Boucle de traitement : actions serveur (extract_text, t2a_decision,
|
||||
# llm_generate)
|
||||
# exécutées entièrement côté serveur jusqu'à trouver une action visuelle
|
||||
# à transmettre à l'Agent V1 ou un pause_for_human qui bloque le replay.
|
||||
action = None
|
||||
@@ -3006,7 +3010,18 @@ async def get_next_action(session_id: str, machine_id: str = "default"):
|
||||
owning_replay["status"] = "paused_need_help"
|
||||
queue.pop(0)
|
||||
_replay_queues[session_id] = queue
|
||||
return {"action": None, "session_id": session_id, "machine_id": machine_id}
|
||||
# Inclure replay_paused/pause_message/replay_id dès ce return
|
||||
# pour que la bulle Léa s'affiche au PREMIER polling (sinon
|
||||
# gap 1-2s où le chat est vide). Aligné sur la branche
|
||||
# target_not_found ligne 2869-2876.
|
||||
return {
|
||||
"action": None,
|
||||
"session_id": session_id,
|
||||
"machine_id": machine_id,
|
||||
"replay_paused": True,
|
||||
"pause_message": owning_replay.get("pause_message", "Validation requise"),
|
||||
"replay_id": owning_replay["replay_id"],
|
||||
}
|
||||
|
||||
# Mode autonome sans safety_checks → skip (comportement legacy)
|
||||
logger.info(
|
||||
@@ -3056,6 +3071,39 @@ async def get_next_action(session_id: str, machine_id: str = "default"):
|
||||
),
|
||||
timeout=180,
|
||||
)
|
||||
elif type_ == "llm_generate":
|
||||
await asyncio.wait_for(
|
||||
loop.run_in_executor(
|
||||
None,
|
||||
_handle_llm_generate_action,
|
||||
action, owning_replay,
|
||||
),
|
||||
timeout=180,
|
||||
)
|
||||
elif type_ == "_concat_text_vars":
|
||||
# Action serveur interne (générée par expansion
|
||||
# extract_text_scroll). Concat 2 vars runtime → 1 var finale.
|
||||
# Quasi-instantanée (pas de I/O), mais on la gère via le
|
||||
# même chemin pour cohérence (timeout généreux par sécurité).
|
||||
await asyncio.wait_for(
|
||||
loop.run_in_executor(
|
||||
None,
|
||||
_handle_concat_text_vars_action,
|
||||
action, owning_replay,
|
||||
),
|
||||
timeout=10,
|
||||
)
|
||||
elif type_ == "paste_and_execute":
|
||||
# Invoque scripts/paste_and_execute_linuxdb.sh
|
||||
# (Ctrl+V + Ctrl+Enter via ydotool dans la VM).
|
||||
await asyncio.wait_for(
|
||||
loop.run_in_executor(
|
||||
None,
|
||||
_handle_paste_and_execute_action,
|
||||
action, owning_replay,
|
||||
),
|
||||
timeout=30,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Action serveur {type_} a levé : {e}")
|
||||
queue.pop(0)
|
||||
|
||||
@@ -41,6 +41,7 @@ _ALLOWED_ACTION_TYPES = {
|
||||
"_concat_text_vars", # Action serveur interne (générée par expansion extract_text_scroll)
|
||||
"t2a_decision", # Analyse LLM facturation T2A → variable workflow
|
||||
"llm_generate", # Génération texte libre côté serveur → variable workflow
|
||||
"paste_and_execute", # Bypass NoMachine : ydotool Ctrl+V+Ctrl+Enter dans VM via SSH
|
||||
}
|
||||
|
||||
# Types d'actions exécutées CÔTÉ SERVEUR (jamais transmises à l'Agent V1).
|
||||
@@ -52,6 +53,7 @@ _SERVER_SIDE_ACTION_TYPES = {
|
||||
"t2a_decision",
|
||||
"llm_generate",
|
||||
"_concat_text_vars",
|
||||
"paste_and_execute",
|
||||
}
|
||||
|
||||
# Pause par défaut entre Ctrl+End/Home et la capture suivante (ms).
|
||||
@@ -890,6 +892,11 @@ def _edge_to_normalized_actions(edge, params: Dict[str, Any]) -> List[Dict[str,
|
||||
}
|
||||
return [normalized]
|
||||
|
||||
elif action_type == "paste_and_execute":
|
||||
normalized["type"] = "paste_and_execute"
|
||||
normalized["parameters"] = {}
|
||||
return [normalized]
|
||||
|
||||
elif action_type == "extract_table":
|
||||
normalized["type"] = "extract_table"
|
||||
normalized["parameters"] = {
|
||||
@@ -1216,6 +1223,18 @@ def _handle_t2a_decision_action(
|
||||
dpi_text = (params.get("input_template") or params.get("dpi") or "").strip()
|
||||
model = params.get("model") or None # None → DEFAULT_MODEL
|
||||
|
||||
# Bypass LLM : si static_result est fourni dans les params, on l'utilise
|
||||
# tel quel comme résultat. Utile pour les démos déterministes (pas de
|
||||
# hallucination, pas de latence, pas de truncation de prompt).
|
||||
static_result = params.get("static_result")
|
||||
if isinstance(static_result, dict) and static_result.get("decision"):
|
||||
replay_state.setdefault("variables", {})[output_var] = static_result
|
||||
logger.info(
|
||||
"t2a_decision (STATIC) → variable '%s' decision=%s replay %s",
|
||||
output_var, static_result.get("decision"), replay_state.get("replay_id", "?"),
|
||||
)
|
||||
return True
|
||||
|
||||
if not dpi_text:
|
||||
logger.warning(
|
||||
"t2a_decision : input vide — variable '%s' = {decision: 'INDETERMINE'}", output_var,
|
||||
@@ -1289,6 +1308,18 @@ def _handle_llm_generate_action(
|
||||
or params.get("variable_name")
|
||||
or "generated_text"
|
||||
).strip()
|
||||
|
||||
# Bypass LLM : si static_text est fourni dans les params, on l'utilise
|
||||
# tel quel. Utile pour les démos déterministes.
|
||||
static_text = params.get("static_text")
|
||||
if isinstance(static_text, str) and static_text.strip():
|
||||
replay_state.setdefault("variables", {})[output_var] = static_text
|
||||
logger.info(
|
||||
"llm_generate (STATIC) → variable '%s' (%d chars) replay %s",
|
||||
output_var, len(static_text), replay_state.get("replay_id", "?"),
|
||||
)
|
||||
return True
|
||||
|
||||
prompt = str(params.get("prompt") or "").strip()
|
||||
context = str(params.get("context") or "")
|
||||
model = params.get("model") or None
|
||||
@@ -1384,6 +1415,40 @@ def _handle_concat_text_vars_action(
|
||||
return bool(merged)
|
||||
|
||||
|
||||
def _handle_paste_and_execute_action(
|
||||
action: Dict[str, Any],
|
||||
replay_state: Dict[str, Any],
|
||||
) -> bool:
|
||||
"""Action serveur : invoque scripts/paste_and_execute_linuxdb.sh pour
|
||||
déclencher Ctrl+V + Ctrl+Enter dans DBeaver de la VM via ydotool.
|
||||
Bypasse Léa/NoMachine (Ctrl mangé par NoMachine passive grab).
|
||||
Cf. handoff 2026-05-16_handoff_ydotool_clipboard.md.
|
||||
"""
|
||||
import subprocess
|
||||
script_path = "/home/dom/ai/rpa_vision_v3/scripts/paste_and_execute_linuxdb.sh"
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[script_path],
|
||||
timeout=30,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logger.warning(
|
||||
"paste_and_execute échoué (rc=%d) stderr=%s",
|
||||
result.returncode, (result.stderr or "")[:500],
|
||||
)
|
||||
return False
|
||||
logger.info("paste_and_execute OK replay %s", replay_state.get("replay_id", "?"))
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("paste_and_execute timeout (30s)")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.warning("paste_and_execute exception : %s", e)
|
||||
return False
|
||||
|
||||
|
||||
def _expand_extract_text_scroll(
|
||||
base: Dict[str, Any],
|
||||
final_var: str,
|
||||
|
||||
@@ -127,7 +127,7 @@ def _resolve_by_template_matching(
|
||||
best_scale = 1.0
|
||||
best_anchor_size = (aw, ah)
|
||||
|
||||
for scale in [1.0, 0.9, 1.1, 0.8, 1.2, 0.75, 1.25, 0.6, 1.5, 0.5, 1.75, 2.0]:
|
||||
for scale in [1.0, 0.95, 0.93, 0.9, 1.05, 1.1, 0.85, 0.8, 1.15, 1.2, 0.75, 1.25, 0.6, 1.5, 0.5, 0.4, 0.35, 0.3, 0.25, 1.75, 2.0]:
|
||||
if scale != 1.0:
|
||||
new_w = int(aw * scale)
|
||||
new_h = int(ah * scale)
|
||||
@@ -2107,7 +2107,10 @@ _RESOLUTION_MIN_SCORES: Dict[str, float] = {
|
||||
# Écart maximum toléré entre coords résolues et coords enregistrées
|
||||
# (en fraction d'écran, dans chaque axe). Au-delà, on considère que la
|
||||
# résolution a trouvé un faux positif ailleurs sur l'écran.
|
||||
_RESOLUTION_MAX_DRIFT: float = 0.20
|
||||
# 15 mai 2026 : élargi 0.20 → 0.95 pour respecter le principe "100% vision"
|
||||
# (Dom). Le score template/VLM seul est désormais la source de confiance,
|
||||
# la position de la capture initiale n'est plus contraignante.
|
||||
_RESOLUTION_MAX_DRIFT: float = 0.95
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user