feat(dialog): R2 MVP P0 — DialogResolver + catalogue 10 entrées (flag OFF default)
- agent_v0/server_v1/core/dialog/ : catalogue compact + DialogResolver stateless (match titre + evidence, trichotomie stricte auto/pause/skip). - 10 entrées P0 : confirm-save-overwrite, notepad-unsaved-changes, windows-file-explorer (fallback replay 4c38dbb8), easily-save/overwrite/ confirm-action/clinical-warning, windows-uac, windows-hello-credui, edge-update. - Validateur déclaratif `system_modals_cannot_be_overridden` : rejette toute surcharge auto/skip sur modaux SYSTÈME (windows-/defender-). - Endpoint POST /api/v1/dialog/resolve derrière flag RPA_DIALOG_RESOLVER_ENABLED (OFF par défaut → 503). Aucun rebranchement côté agent_v1 (executor.py inchangé, P1 plus tard). - 25 tests pytest passants (19 unit + 6 intégration HTTP). Spec : docs/recherche/SPEC_POPUPS_CATALOGUE.md §2bis / §3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5720,6 +5720,66 @@ async def agents_fleet():
|
||||
}
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# R2 MVP P0 — DialogResolver (catalogue centralisé des modaux runtime)
|
||||
# Flag OFF par défaut. Activer en posant RPA_DIALOG_RESOLVER_ENABLED=true.
|
||||
# Le rebranchement côté agent_v1 (consommation de cet endpoint au runtime)
|
||||
# vient en P1 — pour l'instant l'endpoint est exposé "prêt à consommer".
|
||||
# =========================================================================
|
||||
from .core.dialog import DialogResolver as _DialogResolver # noqa: E402
|
||||
|
||||
|
||||
def _dialog_resolver_enabled() -> bool:
|
||||
"""Flag d'activation R2 — lu à chaque appel pour faciliter les tests."""
|
||||
return os.environ.get("RPA_DIALOG_RESOLVER_ENABLED", "").lower() in (
|
||||
"1", "true", "yes", "on",
|
||||
)
|
||||
|
||||
|
||||
# Instance partagée — le résolveur est stateless (cf. resolver.py).
|
||||
_dialog_resolver_singleton = _DialogResolver()
|
||||
|
||||
|
||||
class DialogResolveRequest(BaseModel):
|
||||
"""Payload de l'endpoint ``/api/v1/dialog/resolve`` (P0)."""
|
||||
|
||||
current_title: str
|
||||
evidence_texts: List[str] = []
|
||||
machine_id: Optional[str] = None
|
||||
|
||||
|
||||
@app.post("/api/v1/dialog/resolve")
|
||||
async def dialog_resolve(payload: DialogResolveRequest):
|
||||
"""Renvoyer la résolution d'un modal pour un titre + evidences donnés.
|
||||
|
||||
Réponse :
|
||||
{
|
||||
"matched": bool,
|
||||
"dialog_id": str,
|
||||
"policy": "auto" | "pause" | "skip",
|
||||
"action": {...} | None
|
||||
}
|
||||
|
||||
Si le flag ``RPA_DIALOG_RESOLVER_ENABLED`` n'est pas positionné,
|
||||
l'endpoint retourne 503 (désactivé par défaut — aucun risque de
|
||||
régression sur le pipeline existant).
|
||||
"""
|
||||
if not _dialog_resolver_enabled():
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=(
|
||||
"DialogResolver désactivé (flag RPA_DIALOG_RESOLVER_ENABLED). "
|
||||
"P0 : endpoint exposé mais OFF par défaut."
|
||||
),
|
||||
)
|
||||
|
||||
resolution = _dialog_resolver_singleton.resolve(
|
||||
current_title=payload.current_title,
|
||||
evidence_texts=payload.evidence_texts,
|
||||
)
|
||||
return resolution.to_dict()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
|
||||
Reference in New Issue
Block a user