diff --git a/rust/loki-rs/src/chat/sessions.rs b/rust/loki-rs/src/chat/sessions.rs index a3dc7c3..f0740a7 100644 --- a/rust/loki-rs/src/chat/sessions.rs +++ b/rust/loki-rs/src/chat/sessions.rs @@ -14,7 +14,8 @@ //! //! Ce qui vit hors de la conversation reste derrière [`SessionHooks`] : les //! fichiers, travaux, critères et mode d'une discussion supprimée, ses -//! résultats d'outils, et le préchauffage après une bascule. Le changement de +//! résultats d'outils, le préchauffage après une bascule, et la discussion +//! active vue par les outils du Go ([`GoSessionHooks`]). Le changement de //! projet (`projectSwitch`) reste au Go pour l'instant. use std::future::Future; @@ -44,6 +45,44 @@ pub trait SessionHooks: Send + Sync + 'static { fn dropped(&self, id: &str) -> impl Future + Send; /// Bascule faite (`prewarmAfterSwitch`). fn switched(&self, id: &str) -> impl Future + Send; + /// La discussion ouverte a changé (chargement, création, bascule) : ce + /// qui lit l'identifiant actif ailleurs (outils du Go) doit suivre. + fn activated(&self, _id: &str) -> impl Future + Send { + async {} + } +} + +/// Les effets réels, par l'API interne du Go. +pub struct GoSessionHooks(pub Store); + +impl SessionHooks for GoSessionHooks { + async fn dropped(&self, id: &str) { + let body = serde_json::json!({ "id": id }); + if let Err(e) = self + .0 + .internal_json( + hyper::Method::POST, + "/internal/v1/conv/dropped", + Some(&body), + ) + .await + { + eprintln!("[conv] nettoyage de la discussion {id} non fait : {e}"); + } + } + + async fn switched(&self, _id: &str) {} + + async fn activated(&self, id: &str) { + let body = serde_json::json!({ "id": id }); + if let Err(e) = self + .0 + .internal_json(hyper::Method::POST, "/internal/v1/conv/active", Some(&body)) + .await + { + eprintln!("[conv] discussion active non transmise au Go ({id}) : {e}"); + } + } } /// Refus d'une opération sur les discussions. @@ -169,6 +208,7 @@ impl Sessions { match self.store.get_open(BK_CHAT, &conv_key(&id)).await { Ok(body) => { self.conv.load_from(&id, &body.unwrap_or_default()); + self.hooks.activated(&id).await; return Ok(()); } Err(e) => last_err = e, @@ -241,6 +281,7 @@ impl Sessions { .flatten() .unwrap_or_default(); self.conv.load_from(id, &body); + self.hooks.activated(id).await; Ok(()) } diff --git a/rust/loki-rs/tests/common/fakego.rs b/rust/loki-rs/tests/common/fakego.rs index 01c001d..38ab5d5 100644 --- a/rust/loki-rs/tests/common/fakego.rs +++ b/rust/loki-rs/tests/common/fakego.rs @@ -40,6 +40,8 @@ pub struct FakeGo { /// Résultat imposé à tout outil (sinon « exec: »), et archive recall. pub tool_result: Mutex>, pub recall: Mutex>, + /// Discussion active annoncée et discussions supprimées. + pub conv_log: Mutex>, /// Adresse d'écoute du faux Go (pour un client `Store` direct). pub backend: Mutex>, } @@ -179,6 +181,16 @@ async fn handle(st: Arc, req: Request) -> Response serde_json::json!({"id": format!("r{}", r.len())}).to_string(), ) } + "/internal/v1/conv/active" | "/internal/v1/conv/dropped" => { + let body = req.into_body().collect().await.unwrap().to_bytes(); + let v: serde_json::Value = serde_json::from_slice(&body).unwrap_or_default(); + let what = path.rsplit('/').next().unwrap(); + st.conv_log + .lock() + .unwrap() + .push(format!("{what} {}", v["id"].as_str().unwrap_or_default())); + resp(204, "") + } "/internal/v1/engine/stop" => { st.lease_log.lock().unwrap().push("stop".into()); *st.go_busy.lock().unwrap() = None; diff --git a/rust/loki-rs/tests/lot6.rs b/rust/loki-rs/tests/lot6.rs index 7f73513..b60a334 100644 --- a/rust/loki-rs/tests/lot6.rs +++ b/rust/loki-rs/tests/lot6.rs @@ -258,6 +258,9 @@ impl SessionHooks for Crochets { async fn switched(&self, id: &str) { self.0.lock().unwrap().push(format!("switched {id}")); } + async fn activated(&self, id: &str) { + self.0.lock().unwrap().push(format!("activated {id}")); + } } async fn sessions() -> ( @@ -361,9 +364,19 @@ async fn creer_basculer_renommer() { assert_eq!(textes(&s), ["seulement dans A"]); s.switch(&b).await.unwrap(); assert_eq!(textes(&s), ["seulement dans B"]); + let switched: Vec = + h.0.lock() + .unwrap() + .iter() + .filter(|l| l.starts_with("switched")) + .cloned() + .collect(); + assert_eq!(switched, [format!("switched {a}"), format!("switched {b}")]); + let log = h.0.lock().unwrap().clone(); assert_eq!( - *h.0.lock().unwrap(), - [format!("switched {a}"), format!("switched {b}")] + log[log.len() - 2..], + [format!("activated {b}"), format!("switched {b}")], + "la discussion active suit chaque bascule" ); // L'index porte les titres déduits. let idx = s.read_index().await; @@ -610,3 +623,27 @@ async fn tour_complet_par_le_go() { .collect(); assert_eq!(answer, "Voilà."); } + +#[tokio::test] +async fn crochets_go_des_discussions() { + let (st, go, _stop, _t) = store().await; + let s = Sessions::new( + st.clone(), + Conversation::new(), + loki_rs::chat::sessions::GoSessionHooks(st), + ); + s.load().await.unwrap(); + let a = s.conv().id(); + let b = s.new_conv().await.unwrap(); + s.delete(&b).await.unwrap(); + let log = go.conv_log.lock().unwrap().clone(); + assert_eq!( + log, + [ + format!("active {a}"), + format!("active {b}"), + format!("dropped {b}"), + format!("active {a}") + ] + ); +}