backup: snapshot post-démo GHT 2026-05-19
Some checks failed
tests / Lint (ruff + black) (push) Successful in 1m50s
tests / Tests unitaires (sans GPU) (push) Failing after 1m50s
tests / Tests sécurité (critique) (push) Has been skipped

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:
Dom
2026-05-19 14:55:06 +02:00
parent f2212e77e3
commit 5ea4960e65
627 changed files with 211348 additions and 169 deletions

View File

@@ -18,8 +18,10 @@ from agent_v0.server_v1.replay_engine import (
_resolve_runtime_vars,
_handle_extract_text_action,
_handle_t2a_decision_action,
_handle_concat_text_vars_action,
_edge_to_normalized_actions,
_create_replay_state,
SCROLL_PAUSE_MS,
)
from visual_workflow_builder.backend.services.learned_workflow_bridge import (
VWB_ACTION_TO_CORE,
@@ -92,7 +94,9 @@ def test_t2a_decision_in_allowed():
def test_server_side_types():
assert _SERVER_SIDE_ACTION_TYPES == {"extract_text", "t2a_decision"}
# Set extensible : on vérifie au minimum les 2 types historiques.
# extract_text_scroll ajoute `_concat_text_vars` (action serveur interne).
assert {"extract_text", "t2a_decision"}.issubset(_SERVER_SIDE_ACTION_TYPES)
# ----------------------------------------------------------------------
@@ -280,3 +284,165 @@ def test_export_workflow_with_t2a_chain():
# Vérifier que le templating est bien transporté
t2a_edge = next(e for e in core["edges"] if e["action"]["type"] == "t2a_decision")
assert t2a_edge["action"]["parameters"]["input_template"] == "{{dpi}}"
# ----------------------------------------------------------------------
# extract_text_scroll — expansion + handler concat
# ----------------------------------------------------------------------
def test_extract_text_scroll_in_allowed():
assert "extract_text_scroll" in _ALLOWED_ACTION_TYPES
assert "_concat_text_vars" in _ALLOWED_ACTION_TYPES
def test_extract_text_scroll_concat_is_server_side():
assert "_concat_text_vars" in _SERVER_SIDE_ACTION_TYPES
def test_edge_to_action_extract_text_scroll_expands_to_six_steps():
edge = _FakeEdge(_FakeAction(
"extract_text_scroll",
parameters={"variable_name": "t_full", "paragraph": True},
))
actions = _edge_to_normalized_actions(edge, params={})
# 6 actions : OCR(top), Ctrl+End, wait, OCR(bottom), concat, Ctrl+Home
assert len(actions) == 6
types = [a["type"] for a in actions]
assert types == [
"extract_text",
"key_combo",
"wait",
"extract_text",
"_concat_text_vars",
"key_combo",
]
# Sub-actions OCR utilisent des vars internes différentes
assert actions[0]["parameters"]["output_var"] == "__t_full_top"
assert actions[3]["parameters"]["output_var"] == "__t_full_bottom"
# Ctrl+End / Ctrl+Home corrects
assert actions[1]["keys"] == ["ctrl", "end"]
assert actions[5]["keys"] == ["ctrl", "home"]
# Wait = SCROLL_PAUSE_MS
assert actions[2]["duration_ms"] == SCROLL_PAUSE_MS
# Concat lit les bons noms et écrit dans la var finale
concat_params = actions[4]["parameters"]
assert concat_params["top_var"] == "__t_full_top"
assert concat_params["bottom_var"] == "__t_full_bottom"
assert concat_params["output_var"] == "t_full"
assert concat_params["separator"] == "\n\n"
# Tous les action_id sont uniques et toutes les actions héritent de l'edge
action_ids = {a["action_id"] for a in actions}
assert len(action_ids) == 6
for a in actions:
assert a["edge_id"] == "e1"
assert a["from_node"] == "n1"
def test_edge_to_action_extract_text_scroll_default_var_name():
edge = _FakeEdge(_FakeAction("extract_text_scroll", parameters={}))
actions = _edge_to_normalized_actions(edge, params={})
# Default = extracted_text, donc internal vars = __extracted_text_top/bottom
assert actions[0]["parameters"]["output_var"] == "__extracted_text_top"
assert actions[4]["parameters"]["output_var"] == "extracted_text"
def test_edge_to_action_extract_text_scroll_accepts_output_var_legacy():
"""Compat : `output_var` accepté en plus de `variable_name`."""
edge = _FakeEdge(_FakeAction(
"extract_text_scroll",
parameters={"output_var": "legacy_var"},
))
actions = _edge_to_normalized_actions(edge, params={})
assert actions[4]["parameters"]["output_var"] == "legacy_var"
def test_handle_concat_text_vars_merges_top_and_bottom():
state = _create_replay_state("rep1", "wf", "sess", 3)
state["variables"] = {
"__t_full_top": "Lignes du haut",
"__t_full_bottom": "Lignes du bas",
"other": "intact",
}
action = {
"type": "_concat_text_vars",
"parameters": {
"top_var": "__t_full_top",
"bottom_var": "__t_full_bottom",
"output_var": "t_full",
"separator": "\n\n",
},
}
ok = _handle_concat_text_vars_action(action, state)
assert ok is True
assert state["variables"]["t_full"] == "Lignes du haut\n\nLignes du bas"
# Variables internes nettoyées
assert "__t_full_top" not in state["variables"]
assert "__t_full_bottom" not in state["variables"]
# Autres variables préservées
assert state["variables"]["other"] == "intact"
def test_handle_concat_text_vars_handles_empty_top():
state = _create_replay_state("rep1", "wf", "sess", 3)
state["variables"] = {"__a_top": "", "__a_bottom": "Bas seul"}
action = {
"type": "_concat_text_vars",
"parameters": {
"top_var": "__a_top",
"bottom_var": "__a_bottom",
"output_var": "a",
"separator": "\n\n",
},
}
ok = _handle_concat_text_vars_action(action, state)
assert ok is True
# Pas de séparateur en début/fin si une var est vide
assert state["variables"]["a"] == "Bas seul"
def test_handle_concat_text_vars_handles_both_empty():
state = _create_replay_state("rep1", "wf", "sess", 3)
state["variables"] = {"__a_top": "", "__a_bottom": ""}
action = {
"type": "_concat_text_vars",
"parameters": {
"top_var": "__a_top",
"bottom_var": "__a_bottom",
"output_var": "a",
},
}
ok = _handle_concat_text_vars_action(action, state)
assert ok is False # rien d'utile produit
assert state["variables"]["a"] == ""
def test_handle_concat_text_vars_preserves_user_named_vars():
"""Si top/bottom ne commencent pas par __ on ne les supprime pas."""
state = _create_replay_state("rep1", "wf", "sess", 3)
state["variables"] = {"user_top": "haut", "user_bottom": "bas"}
action = {
"type": "_concat_text_vars",
"parameters": {
"top_var": "user_top",
"bottom_var": "user_bottom",
"output_var": "merged",
},
}
_handle_concat_text_vars_action(action, state)
assert state["variables"]["user_top"] == "haut"
assert state["variables"]["user_bottom"] == "bas"
assert state["variables"]["merged"] == "haut\n\nbas"
def test_vwb_extract_text_scroll_passthrough():
assert VWB_ACTION_TO_CORE["extract_text_scroll"] == "extract_text_scroll"
def test_vwb_params_extract_text_scroll_preserves_variable_name():
p = _vwb_params_to_core("extract_text_scroll", {"variable_name": "t_full"})
assert p == {"variable_name": "t_full"}
def test_vwb_params_extract_text_scroll_legacy_output_var():
p = _vwb_params_to_core("extract_text_scroll", {"output_var": "legacy"})
assert p["variable_name"] == "legacy"