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;
|
use super::format_user_prompt;
|
||||||
|
|
||||||
/// Text posted to the room by [`spawn_digging_in_watcher`] when a turn runs
|
/// Text posted to the room by [`spawn_digging_in_watcher`] when a turn runs
|
||||||
/// long without emitting any user-facing text.
|
/// long without emitting any user-facing text, and by `on_room_message` as
|
||||||
const DIGGING_IN_MESSAGE: &str = "Working...";
|
/// 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
|
/// Spawns a background watcher that posts a single "digging in" notice to
|
||||||
/// `room_id` if `threshold` elapses before `sent_any_text` becomes `true`.
|
/// `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(
|
pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||||
ev: OriginalSyncRoomMessageEvent,
|
ev: OriginalSyncRoomMessageEvent,
|
||||||
room: Room,
|
room: Room,
|
||||||
@@ -1565,6 +1585,10 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
|||||||
return;
|
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
|
// Hand the message to the protocol-agnostic dispatcher instead of spawning
|
||||||
// directly. The dispatcher applies a coalesce window and a per-session
|
// directly. The dispatcher applies a coalesce window and a per-session
|
||||||
// serial lock, preventing duplicate concurrent Timmy spawns.
|
// 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 {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
eval_gateway_overview_command, eval_gateway_status_command, eval_switch_command,
|
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::chat::{ChatTransport, MessageId};
|
||||||
use crate::http::context::QuestionAnswer;
|
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"
|
"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