huskies: merge 1063
This commit is contained in:
@@ -13,7 +13,7 @@ use super::super::context::BotContext;
|
||||
use super::super::format::markdown_to_html;
|
||||
use super::super::history::{ConversationEntry, ConversationRole, save_history};
|
||||
|
||||
use super::format_user_prompt;
|
||||
use super::{format_drained_events, format_user_prompt};
|
||||
|
||||
pub(in crate::chat::transport::matrix::bot) async fn handle_message(
|
||||
room_id_str: String,
|
||||
@@ -30,28 +30,18 @@ pub(in crate::chat::transport::matrix::bot) async fn handle_message(
|
||||
guard.get(&room_id).and_then(|conv| conv.session_id.clone())
|
||||
};
|
||||
|
||||
// Drain any pipeline transition events buffered since the last LLM turn and
|
||||
// prepend them as a passive <system-reminder> block so Timmy sees pipeline
|
||||
// activity without requiring a separate message. Capped at 20 lines to
|
||||
// keep context size bounded.
|
||||
const MAX_PIPELINE_EVENTS: usize = 20;
|
||||
// Drain pipeline and gateway transition events buffered since the last LLM
|
||||
// turn and prepend them as a passive <system-reminder> block so Timmy sees
|
||||
// pipeline activity without requiring a separate message. Sled events come
|
||||
// from `pending_pipeline_events`; gateway events from `pending_gateway_events`.
|
||||
// In practice only one buffer is non-empty (sled mode vs gateway mode).
|
||||
let system_reminder_prefix = {
|
||||
let mut guard = ctx.pending_pipeline_events.lock().await;
|
||||
if guard.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
let total = guard.len();
|
||||
let lines: Vec<String> = guard.drain(..).collect();
|
||||
drop(guard);
|
||||
let shown_count = total.min(MAX_PIPELINE_EVENTS);
|
||||
let shown = lines[..shown_count].join("\n");
|
||||
let tail = if total > MAX_PIPELINE_EVENTS {
|
||||
format!("\n...and {} more", total - MAX_PIPELINE_EVENTS)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
format!("<system-reminder>\n{shown}{tail}\n</system-reminder>\n")
|
||||
}
|
||||
let mut sled_guard = ctx.pending_pipeline_events.lock().await;
|
||||
let mut gtw_guard = ctx.pending_gateway_events.lock().await;
|
||||
let all_lines: Vec<String> = sled_guard.drain(..).chain(gtw_guard.drain(..)).collect();
|
||||
drop(sled_guard);
|
||||
drop(gtw_guard);
|
||||
format_drained_events(all_lines)
|
||||
};
|
||||
|
||||
// The prompt is just the current message with sender attribution.
|
||||
|
||||
@@ -11,6 +11,27 @@ pub(super) fn format_user_prompt(sender: &str, message: &str) -> String {
|
||||
format!("{sender}: {message}")
|
||||
}
|
||||
|
||||
/// Drain `lines` into a `<system-reminder>` block for injection at the head of
|
||||
/// the next LLM prompt. Returns an empty string when `lines` is empty.
|
||||
///
|
||||
/// At most 20 lines are shown verbatim; excess lines are replaced with a
|
||||
/// `…and N more` indicator to keep context size bounded.
|
||||
pub(in crate::chat::transport::matrix::bot) fn format_drained_events(lines: Vec<String>) -> String {
|
||||
if lines.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
const MAX_PIPELINE_EVENTS: usize = 20;
|
||||
let total = lines.len();
|
||||
let shown_count = total.min(MAX_PIPELINE_EVENTS);
|
||||
let shown = lines[..shown_count].join("\n");
|
||||
let tail = if total > MAX_PIPELINE_EVENTS {
|
||||
format!("\n...and {} more", total - MAX_PIPELINE_EVENTS)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
format!("<system-reminder>\n{shown}{tail}\n</system-reminder>\n")
|
||||
}
|
||||
|
||||
/// Matrix event handler for room messages. Each invocation spawns an
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -51,6 +72,49 @@ mod tests {
|
||||
assert!(crate::llm::oauth::extract_login_url_from_error(err).is_none());
|
||||
}
|
||||
|
||||
// -- format_drained_events ----------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn format_drained_events_empty_returns_empty_string() {
|
||||
assert_eq!(format_drained_events(vec![]), String::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_drained_events_wraps_in_system_reminder() {
|
||||
let result = format_drained_events(vec!["audit ts=2026 id=1 event=x".to_string()]);
|
||||
assert!(result.starts_with("<system-reminder>\n"), "got: {result}");
|
||||
assert!(result.ends_with("</system-reminder>\n"), "got: {result}");
|
||||
assert!(
|
||||
result.contains("audit ts=2026 id=1 event=x"),
|
||||
"got: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_drained_events_caps_at_20_with_overflow_indicator() {
|
||||
let lines: Vec<String> = (0..25).map(|i| format!("line {i}")).collect();
|
||||
let result = format_drained_events(lines);
|
||||
assert!(result.contains("...and 5 more"), "got: {result}");
|
||||
assert!(
|
||||
result.contains("line 19"),
|
||||
"last shown line missing; got: {result}"
|
||||
);
|
||||
assert!(
|
||||
!result.contains("line 20"),
|
||||
"line 21 must be hidden; got: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_drained_events_exactly_20_no_overflow_indicator() {
|
||||
let lines: Vec<String> = (0..20).map(|i| format!("line {i}")).collect();
|
||||
let result = format_drained_events(lines);
|
||||
assert!(
|
||||
!result.contains("...and"),
|
||||
"must not show overflow when exactly 20; got: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
// -- bot_name / system prompt -------------------------------------------
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user