huskies: merge 1239 story Matrix bot posts \"Working...\" immediately on message receipt
This commit is contained in:
@@ -18,8 +18,9 @@ use super::super::history::{ConversationEntry, ConversationRole, save_history};
|
||||
use super::format_user_prompt;
|
||||
|
||||
/// Text posted to the room by [`spawn_digging_in_watcher`] when a turn runs
|
||||
/// long without emitting any user-facing text.
|
||||
const DIGGING_IN_MESSAGE: &str = "Working...";
|
||||
/// long without emitting any user-facing text, and by `on_room_message` as
|
||||
/// an immediate acknowledgement when a message is first received (story 1239).
|
||||
pub(super) const DIGGING_IN_MESSAGE: &str = "Working...";
|
||||
|
||||
/// Spawns a background watcher that posts a single "digging in" notice to
|
||||
/// `room_id` if `threshold` elapses before `sent_any_text` becomes `true`.
|
||||
|
||||
@@ -390,6 +390,26 @@ fn parse_question_reply(
|
||||
}
|
||||
}
|
||||
|
||||
/// Post an immediate "Working..." acknowledgement for a newly received room
|
||||
/// message, before it is handed to the dispatcher (which may coalesce it
|
||||
/// with other messages and/or queue it behind an in-flight run).
|
||||
///
|
||||
/// Called once per incoming room message that reaches the LLM dispatch path
|
||||
/// — never once per resulting agent turn — so a burst of messages the
|
||||
/// dispatcher later coalesces into a single turn still yields one notice per
|
||||
/// message the user actually sent (story 1239).
|
||||
async fn post_working_notice(ctx: &BotContext, room_id_str: &str) {
|
||||
let html = markdown_to_html(handle_message::DIGGING_IN_MESSAGE);
|
||||
if let Ok(msg_id) = ctx
|
||||
.transport
|
||||
.send_message(room_id_str, handle_message::DIGGING_IN_MESSAGE, &html)
|
||||
.await
|
||||
&& let Ok(event_id) = msg_id.parse()
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
ev: OriginalSyncRoomMessageEvent,
|
||||
room: Room,
|
||||
@@ -1565,6 +1585,10 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
return;
|
||||
}
|
||||
|
||||
// Acknowledge receipt immediately, before the message is handed to the
|
||||
// dispatcher below (story 1239).
|
||||
post_working_notice(&ctx, &room_id_str).await;
|
||||
|
||||
// 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.
|
||||
@@ -1604,7 +1628,8 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
mod tests {
|
||||
use super::{
|
||||
eval_gateway_overview_command, eval_gateway_status_command, eval_switch_command,
|
||||
parse_question_reply, try_handle_compact_command, try_handle_stop_command,
|
||||
parse_question_reply, post_working_notice, try_handle_compact_command,
|
||||
try_handle_stop_command,
|
||||
};
|
||||
use crate::chat::{ChatTransport, MessageId};
|
||||
use crate::http::context::QuestionAnswer;
|
||||
@@ -2279,4 +2304,57 @@ mod tests {
|
||||
"no reply should be sent for a message that isn't a bare stop"
|
||||
);
|
||||
}
|
||||
|
||||
// ── post_working_notice (story 1239) ─────────────────────────────────
|
||||
|
||||
/// AC1/AC2: receiving a message posts "Working..." to the room
|
||||
/// immediately — before any agent/dispatcher work happens — via a
|
||||
/// single, synchronously-awaited call.
|
||||
#[tokio::test]
|
||||
async fn post_working_notice_sends_immediately_to_the_room() {
|
||||
use std::sync::Arc;
|
||||
|
||||
let project_root_dir = tempfile::tempdir().unwrap();
|
||||
let services = crate::services::Services::new_test(
|
||||
project_root_dir.path().to_path_buf(),
|
||||
"Huskies".to_string(),
|
||||
);
|
||||
let transport = Arc::new(CapturingTransport::new());
|
||||
let ctx = make_test_ctx(services, transport.clone());
|
||||
|
||||
post_working_notice(&ctx, "!room:example.com").await;
|
||||
|
||||
let sent = transport.sent.lock().unwrap().clone();
|
||||
assert_eq!(sent.len(), 1, "exactly one notice must be sent");
|
||||
assert_eq!(sent[0].0, "!room:example.com");
|
||||
assert_eq!(sent[0].1, "Working...");
|
||||
}
|
||||
|
||||
/// AC3: the notice is emitted once per call — i.e. once per incoming
|
||||
/// user message — not coalesced into a single notice the way the
|
||||
/// dispatcher coalesces messages into one agent turn. Two messages
|
||||
/// (two calls) must yield two notices.
|
||||
#[tokio::test]
|
||||
async fn post_working_notice_fires_once_per_message_not_once_per_turn() {
|
||||
use std::sync::Arc;
|
||||
|
||||
let project_root_dir = tempfile::tempdir().unwrap();
|
||||
let services = crate::services::Services::new_test(
|
||||
project_root_dir.path().to_path_buf(),
|
||||
"Huskies".to_string(),
|
||||
);
|
||||
let transport = Arc::new(CapturingTransport::new());
|
||||
let ctx = make_test_ctx(services, transport.clone());
|
||||
|
||||
post_working_notice(&ctx, "!room:example.com").await;
|
||||
post_working_notice(&ctx, "!room:example.com").await;
|
||||
|
||||
let sent = transport.sent.lock().unwrap().clone();
|
||||
assert_eq!(
|
||||
sent.len(),
|
||||
2,
|
||||
"two incoming messages must produce two notices, even though the \
|
||||
dispatcher would coalesce them into a single agent turn"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user