fix(chat): réattache persistante CLI custom et rendu tools/MCP/progress enrichi (#188)
Corrige la perte de session à la navigation: le démontage du CustomAgentChatView appelait close_agent_session (destructeur) au lieu de simplement détacher le transport. Désormais: - nouvelle commande Tauri detach_agent_chat (+ ChatBridge::detach) qui libère le canal sans tuer la session ni le scrollback retenu; distinct de close_agent_session qui reste le close destructeur explicite; - le frontend appelle detachAgentChat à l'unmount puis restore sa vue depuis un cache de session (turns + chunkCount) à la réattache; - message « Reprise effectuée: N événements rattrapés… » quand la réattache rejoue plus de chunks que le cache local n'en avait conservé. Enrichit le rendu tools/MCP/progress: - ProgressSettler (chat bridge) émet un progress completed synthétique quand un spinner reste ouvert (nouvelle entité progress, chunk text/final/error): fini les spinners fantômes; - les adapters claude/codex/opencode/openai_compat extraient un aperçu du payload (arguments/input/command/message/params) attaché au progress; - bounded_reply_progress_preview (domain) garde les 4 premières lignes non vides, normalisation CR/LF; - previewText (frontend) multiligne whitespace-pre-wrap, source/nativeEvent affichés sous le titre; suppression du flag settled et du Spinner inline devenus redondants (l'état est porté par le stage + le settler). Tests: detach_agent_chat gateway (Tauri + mock), ProgressSettler, bornage reply_progress, item_preview/tool_preview par adapter, réattache avec cache et notice de rattrapage.
This commit is contained in:
@ -688,7 +688,7 @@ impl ReplyProgress {
|
||||
/// Ajoute un extrait textuel.
|
||||
#[must_use]
|
||||
pub fn with_text(mut self, text: impl Into<String>) -> Self {
|
||||
self.text = Some(text.into());
|
||||
self.text = bounded_reply_progress_preview(text.into());
|
||||
self
|
||||
}
|
||||
|
||||
@ -712,6 +712,57 @@ impl ReplyProgress {
|
||||
}
|
||||
}
|
||||
|
||||
fn bounded_reply_progress_preview(text: String) -> Option<String> {
|
||||
let mut lines = Vec::new();
|
||||
let normalized = text.replace("\r\n", "\n").replace('\r', "\n");
|
||||
for line in normalized.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
lines.push(trimmed.to_owned());
|
||||
if lines.len() == 4 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if lines.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(lines.join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod reply_progress_tests {
|
||||
use super::{ReplyProgress, ReplyProgressKind, ReplyProgressSource, ReplyProgressStage};
|
||||
|
||||
#[test]
|
||||
fn with_text_keeps_first_four_non_blank_lines() {
|
||||
let progress = ReplyProgress::new(
|
||||
ReplyProgressSource::ProviderNative,
|
||||
ReplyProgressKind::Tool,
|
||||
ReplyProgressStage::Started,
|
||||
"tool",
|
||||
)
|
||||
.with_text(" one \n\n two\r\n three\r four\n five ");
|
||||
|
||||
assert_eq!(progress.text.as_deref(), Some("one\ntwo\nthree\nfour"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_text_drops_blank_preview() {
|
||||
let progress = ReplyProgress::new(
|
||||
ReplyProgressSource::ProviderNative,
|
||||
ReplyProgressKind::Tool,
|
||||
ReplyProgressStage::Started,
|
||||
"tool",
|
||||
)
|
||||
.with_text(" \n\t\r\n ");
|
||||
|
||||
assert_eq!(progress.text, None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Un événement incrémental d'un tour de réponse d'un agent IA (ARCHITECTURE §17.1).
|
||||
///
|
||||
/// Universel : l'adapter (Claude/Codex) traduit SON format structuré documenté
|
||||
|
||||
Reference in New Issue
Block a user