huskies: merge 1093 bug Chat dispatcher spawns one Timmy per inbound message — needs coalesce window + per-session serial lock
This commit is contained in:
@@ -21,6 +21,7 @@ pub(in crate::chat::transport::matrix::bot) async fn handle_message(
|
||||
ctx: BotContext,
|
||||
sender: String,
|
||||
user_message: String,
|
||||
mut cancel_rx: watch::Receiver<bool>,
|
||||
) {
|
||||
// Look up the room's existing Claude Code session ID (if any) so we can
|
||||
// resume the conversation with structured API messages instead of
|
||||
@@ -68,9 +69,6 @@ pub(in crate::chat::transport::matrix::bot) async fn handle_message(
|
||||
);
|
||||
|
||||
let provider = ClaudeCodeProvider::new();
|
||||
let (cancel_tx, mut cancel_rx) = watch::channel(false);
|
||||
// Keep the sender alive for the duration of the call.
|
||||
let _cancel_tx = cancel_tx;
|
||||
|
||||
// Channel for sending complete paragraphs to the Matrix posting task.
|
||||
let (msg_tx, mut msg_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
|
||||
|
||||
@@ -608,9 +608,56 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn a separate task so the Matrix sync loop is not blocked while we
|
||||
// wait for the LLM response (which can take several seconds).
|
||||
tokio::spawn(async move {
|
||||
handle_message(room_id_str, incoming_room_id, ctx, sender, user_message).await;
|
||||
});
|
||||
// "stop" — cancel the running LLM turn for this session and clear pending queue.
|
||||
{
|
||||
let stripped = crate::chat::util::strip_bot_mention(
|
||||
&user_message,
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
)
|
||||
.trim()
|
||||
.to_ascii_lowercase();
|
||||
if stripped == "stop" {
|
||||
slog!("[matrix-bot] stop command from {sender} for session {room_id_str}");
|
||||
ctx.services.chat_dispatcher.stop(&room_id_str);
|
||||
let msg = "Stopped.";
|
||||
let html = markdown_to_html(msg);
|
||||
if let Ok(msg_id) = ctx.transport.send_message(&room_id_str, msg, &html).await
|
||||
&& let Ok(event_id) = msg_id.parse()
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hand the message to the protocol-agnostic dispatcher instead of spawning
|
||||
// directly. The dispatcher applies a coalesce window and a per-session
|
||||
// serial lock, preventing duplicate concurrent Timmy spawns.
|
||||
let ctx_for_factory = ctx.clone();
|
||||
let factory: crate::chat::dispatcher::SpawnFn = {
|
||||
let room_id_str2 = room_id_str.clone();
|
||||
std::sync::Arc::new(
|
||||
move |coalesced: String, cancel_rx: tokio::sync::watch::Receiver<bool>| {
|
||||
let room_id_str = room_id_str2.clone();
|
||||
let incoming_room_id = incoming_room_id.clone();
|
||||
let ctx = ctx_for_factory.clone();
|
||||
let sender = sender.clone();
|
||||
Box::pin(async move {
|
||||
handle_message(
|
||||
room_id_str,
|
||||
incoming_room_id,
|
||||
ctx,
|
||||
sender,
|
||||
coalesced,
|
||||
cancel_rx,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
},
|
||||
)
|
||||
};
|
||||
ctx.services
|
||||
.chat_dispatcher
|
||||
.submit(room_id_str, user_message, factory);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user