story-kit: merge 316_refactor_abstract_bot_transport_layer_for_multi_platform_support
This commit is contained in:
+40
-65
@@ -10,15 +10,11 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use matrix_sdk::room::Room;
|
||||
use matrix_sdk::ruma::OwnedEventId;
|
||||
use matrix_sdk::ruma::events::room::message::{
|
||||
ReplacementMetadata, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
|
||||
};
|
||||
use tokio::sync::{Mutex as TokioMutex, watch};
|
||||
|
||||
use crate::agents::{AgentPool, AgentStatus};
|
||||
use crate::slog;
|
||||
use crate::transport::ChatTransport;
|
||||
|
||||
use super::bot::markdown_to_html;
|
||||
|
||||
@@ -39,7 +35,10 @@ pub struct HtopSession {
|
||||
}
|
||||
|
||||
/// Per-room htop session map type alias.
|
||||
pub type HtopSessions = Arc<TokioMutex<HashMap<matrix_sdk::ruma::OwnedRoomId, HtopSession>>>;
|
||||
///
|
||||
/// Keys are platform-agnostic room ID strings (e.g. `"!abc:example.com"` on
|
||||
/// Matrix) so this type works with any [`ChatTransport`] implementation.
|
||||
pub type HtopSessions = Arc<TokioMutex<HashMap<String, HtopSession>>>;
|
||||
|
||||
/// Parse an htop command from a raw Matrix message body.
|
||||
///
|
||||
@@ -253,40 +252,19 @@ pub fn build_htop_message(agents: &AgentPool, tick: u32, total_duration_secs: u6
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Matrix replacement helper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Edit an existing Matrix message by sending a replacement event.
|
||||
///
|
||||
/// Uses `RoomMessageEventContentWithoutRelation::make_replacement` with
|
||||
/// `ReplacementMetadata` so the replacement carries the original event ID.
|
||||
async fn send_replacement(
|
||||
room: &Room,
|
||||
original_event_id: &OwnedEventId,
|
||||
plain: &str,
|
||||
html: &str,
|
||||
) -> Result<(), String> {
|
||||
let new_content =
|
||||
RoomMessageEventContentWithoutRelation::text_html(plain.to_string(), html.to_string());
|
||||
let metadata = ReplacementMetadata::new(original_event_id.clone(), None);
|
||||
let content = new_content.make_replacement(metadata);
|
||||
|
||||
room.send(content)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("Matrix send error: {e}"))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Background monitoring loop
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Run the htop background loop: update the message every 5 seconds until
|
||||
/// the stop signal is received or the timeout expires.
|
||||
///
|
||||
/// Uses the [`ChatTransport`] abstraction so the loop works with any chat
|
||||
/// platform, not just Matrix.
|
||||
pub async fn run_htop_loop(
|
||||
room: Room,
|
||||
initial_event_id: OwnedEventId,
|
||||
transport: Arc<dyn ChatTransport>,
|
||||
room_id: String,
|
||||
initial_message_id: String,
|
||||
agents: Arc<AgentPool>,
|
||||
mut stop_rx: watch::Receiver<bool>,
|
||||
duration_secs: u64,
|
||||
@@ -303,7 +281,7 @@ pub async fn run_htop_loop(
|
||||
_ = &mut sleep => {}
|
||||
Ok(()) = stop_rx.changed() => {
|
||||
if *stop_rx.borrow() {
|
||||
send_stopped_message(&room, &initial_event_id).await;
|
||||
send_stopped_message(&*transport, &room_id, &initial_message_id).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -311,27 +289,27 @@ pub async fn run_htop_loop(
|
||||
|
||||
// Re-check after waking — the sender might have signalled while we slept.
|
||||
if *stop_rx.borrow() {
|
||||
send_stopped_message(&room, &initial_event_id).await;
|
||||
send_stopped_message(&*transport, &room_id, &initial_message_id).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let text = build_htop_message(&agents, tick as u32, duration_secs);
|
||||
let html = markdown_to_html(&text);
|
||||
|
||||
if let Err(e) = send_replacement(&room, &initial_event_id, &text, &html).await {
|
||||
if let Err(e) = transport.edit_message(&room_id, &initial_message_id, &text, &html).await {
|
||||
slog!("[htop] Failed to update message: {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-stop: timeout reached.
|
||||
send_stopped_message(&room, &initial_event_id).await;
|
||||
send_stopped_message(&*transport, &room_id, &initial_message_id).await;
|
||||
}
|
||||
|
||||
async fn send_stopped_message(room: &Room, event_id: &OwnedEventId) {
|
||||
async fn send_stopped_message(transport: &dyn ChatTransport, room_id: &str, message_id: &str) {
|
||||
let text = "**htop** — monitoring stopped.";
|
||||
let html = markdown_to_html(text);
|
||||
if let Err(e) = send_replacement(room, event_id, text, &html).await {
|
||||
if let Err(e) = transport.edit_message(room_id, message_id, text, &html).await {
|
||||
slog!("[htop] Failed to send stop message: {e}");
|
||||
}
|
||||
}
|
||||
@@ -344,9 +322,11 @@ async fn send_stopped_message(room: &Room, event_id: &OwnedEventId) {
|
||||
///
|
||||
/// Stops any existing session for the room, sends the initial dashboard
|
||||
/// message, and spawns a background task that edits it every 5 seconds.
|
||||
///
|
||||
/// Uses the [`ChatTransport`] abstraction so htop works with any platform.
|
||||
pub async fn handle_htop_start(
|
||||
room: &Room,
|
||||
room_id: &matrix_sdk::ruma::OwnedRoomId,
|
||||
transport: &Arc<dyn ChatTransport>,
|
||||
room_id: &str,
|
||||
htop_sessions: &HtopSessions,
|
||||
agents: Arc<AgentPool>,
|
||||
duration_secs: u64,
|
||||
@@ -357,15 +337,8 @@ pub async fn handle_htop_start(
|
||||
// Send the initial message.
|
||||
let initial_text = build_htop_message(&agents, 0, duration_secs);
|
||||
let initial_html = markdown_to_html(&initial_text);
|
||||
let send_result = room
|
||||
.send(RoomMessageEventContent::text_html(
|
||||
initial_text,
|
||||
initial_html,
|
||||
))
|
||||
.await;
|
||||
|
||||
let event_id = match send_result {
|
||||
Ok(r) => r.event_id,
|
||||
let message_id = match transport.send_message(room_id, &initial_text, &initial_html).await {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
slog!("[htop] Failed to send initial message: {e}");
|
||||
return;
|
||||
@@ -376,18 +349,26 @@ pub async fn handle_htop_start(
|
||||
let (stop_tx, stop_rx) = watch::channel(false);
|
||||
{
|
||||
let mut sessions = htop_sessions.lock().await;
|
||||
sessions.insert(room_id.clone(), HtopSession { stop_tx });
|
||||
sessions.insert(room_id.to_string(), HtopSession { stop_tx });
|
||||
}
|
||||
|
||||
// Spawn the background update loop.
|
||||
let room_clone = room.clone();
|
||||
let transport_clone = Arc::clone(transport);
|
||||
let sessions_clone = Arc::clone(htop_sessions);
|
||||
let room_id_clone = room_id.clone();
|
||||
let room_id_owned = room_id.to_string();
|
||||
tokio::spawn(async move {
|
||||
run_htop_loop(room_clone, event_id, agents, stop_rx, duration_secs).await;
|
||||
run_htop_loop(
|
||||
transport_clone,
|
||||
room_id_owned.clone(),
|
||||
message_id,
|
||||
agents,
|
||||
stop_rx,
|
||||
duration_secs,
|
||||
)
|
||||
.await;
|
||||
// Clean up the session entry when the loop exits naturally.
|
||||
let mut sessions = sessions_clone.lock().await;
|
||||
sessions.remove(&room_id_clone);
|
||||
sessions.remove(&room_id_owned);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -396,18 +377,15 @@ pub async fn handle_htop_start(
|
||||
/// When there is no active session, sends a "no active session" reply
|
||||
/// to the room so the user knows the command was received.
|
||||
pub async fn handle_htop_stop(
|
||||
room: &Room,
|
||||
room_id: &matrix_sdk::ruma::OwnedRoomId,
|
||||
transport: &dyn ChatTransport,
|
||||
room_id: &str,
|
||||
htop_sessions: &HtopSessions,
|
||||
) {
|
||||
let had_session = stop_existing_session(htop_sessions, room_id).await;
|
||||
if !had_session {
|
||||
let msg = "No active htop session in this room.";
|
||||
let html = markdown_to_html(msg);
|
||||
if let Err(e) = room
|
||||
.send(RoomMessageEventContent::text_html(msg, html))
|
||||
.await
|
||||
{
|
||||
if let Err(e) = transport.send_message(room_id, msg, &html).await {
|
||||
slog!("[htop] Failed to send no-session reply: {e}");
|
||||
}
|
||||
}
|
||||
@@ -417,10 +395,7 @@ pub async fn handle_htop_stop(
|
||||
/// Signal and remove the existing session for `room_id`.
|
||||
///
|
||||
/// Returns `true` if a session was found and stopped.
|
||||
async fn stop_existing_session(
|
||||
htop_sessions: &HtopSessions,
|
||||
room_id: &matrix_sdk::ruma::OwnedRoomId,
|
||||
) -> bool {
|
||||
async fn stop_existing_session(htop_sessions: &HtopSessions, room_id: &str) -> bool {
|
||||
let mut sessions = htop_sessions.lock().await;
|
||||
if let Some(session) = sessions.remove(room_id) {
|
||||
// Signal the background task to stop (ignore error — task may be done).
|
||||
|
||||
Reference in New Issue
Block a user