Merge feature/ticket60-ticket-assistant-no-reply into develop (#60)

Rendre visible l'échec « aucune réponse » de l'assistant de ticket :
stream sans Final ou Final vide → ReplyChunk::Error terminal visible,
plus jamais de tour muet. Backend + frontend, tests verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 08:48:04 +02:00
14 changed files with 571 additions and 25 deletions

View File

@ -405,6 +405,13 @@ pub enum ReplyEvent {
/// humain, §21.1 niveau 3). Jamais une `Instant` monotone (cf. §21.2-T1).
resets_at_ms: Option<i64>,
},
/// Erreur terminale du moteur structurée et affichable côté chat. Porte un
/// message déjà nettoyé par l'adapter ; les détails propres à la CLI restent
/// confinés côté infrastructure.
Error {
/// Message humain-lisible à rendre dans la cellule chat.
message: String,
},
/// **Événement terminal déterministe** d'un tour : l'adapter l'émet quand il a
/// lu le message `result` documenté de la CLI. Porte le contenu final agrégé.
/// Après `Final`, le flux se termine (plus aucun événement).
@ -415,11 +422,13 @@ pub enum ReplyEvent {
}
/// Flux borné d'événements de réponse d'UN tour (ARCHITECTURE §17.1). Se termine
/// après le [`ReplyEvent::Final`] (ou sur erreur). Calqué sur [`OutputStream`],
/// après le [`ReplyEvent::Final`] / [`ReplyEvent::Error`] (ou sur erreur de transport).
/// Calqué sur [`OutputStream`],
/// mais **typé** : deltas de texte → activités d'outil → battements de cœur*
/// ([`ReplyEvent::Heartbeat`], non terminaux, en nombre quelconque et entrelacés) →
/// **un** `Final` terminal déterministe, plutôt que des octets bruts. Seul le `Final`
/// clôt le flux ; deltas, activités et heartbeats sont tous non terminaux.
/// **un** terminal déterministe, plutôt que des octets bruts. `Final` clôt un tour
/// réussi ; `Error` clôt un tour échoué avec un message visible. Deltas, activités
/// et heartbeats sont tous non terminaux.
pub type ReplyStream = Box<dyn Iterator<Item = ReplyEvent> + Send>;
/// Description pure d'un outil exposé à un modèle OpenAI-compatible.

View File

@ -85,6 +85,7 @@ impl ReadinessPolicy {
/// l'application (planifier la reprise à `resets_at_ms`). L'heure de reset est
/// propagée telle quelle.
/// - [`ReplyEvent::TextDelta`] / [`ReplyEvent::Announcement`] /
/// [`ReplyEvent::Error`] /
/// [`ReplyEvent::ToolActivity`] /
/// [`ReplyEvent::Heartbeat`] ⇒ `None` : tous **non terminaux** (le flux
/// continue). Un heartbeat prouve la vivacité mais ne termine pas le tour.
@ -97,6 +98,7 @@ impl ReadinessPolicy {
}),
ReplyEvent::TextDelta { .. }
| ReplyEvent::Announcement { .. }
| ReplyEvent::Error { .. }
| ReplyEvent::ToolActivity { .. }
| ReplyEvent::Heartbeat => None,
}