huskies: merge 527_story_remove_rate_limit_hard_block_bot_notifications_from_matrix_chat
This commit is contained in:
@@ -141,30 +141,6 @@ const RATE_LIMIT_DEBOUNCE: Duration = Duration::from_secs(60);
|
||||
/// into a single notification (only the final stage is announced).
|
||||
const STAGE_TRANSITION_DEBOUNCE: Duration = Duration::from_millis(200);
|
||||
|
||||
/// Format a rate limit hard block notification message with scheduled resume time.
|
||||
///
|
||||
/// Returns `(plain_text, html)` suitable for `ChatTransport::send_message`.
|
||||
pub fn format_rate_limit_hard_block_notification(
|
||||
item_id: &str,
|
||||
story_name: Option<&str>,
|
||||
agent_name: &str,
|
||||
resume_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> (String, String) {
|
||||
let number = extract_story_number(item_id).unwrap_or(item_id);
|
||||
let name = story_name.unwrap_or(item_id);
|
||||
let local_time = resume_at.with_timezone(&chrono::Local);
|
||||
let resume_str = local_time.format("%Y-%m-%d %H:%M").to_string();
|
||||
|
||||
let plain = format!(
|
||||
"\u{1f6d1} #{number} {name} \u{2014} {agent_name} hit a hard rate limit; \
|
||||
will auto-resume at {resume_str}"
|
||||
);
|
||||
let html = format!(
|
||||
"\u{1f6d1} <strong>#{number}</strong> <em>{name}</em> \u{2014} \
|
||||
{agent_name} hit a hard rate limit; will auto-resume at {resume_str}"
|
||||
);
|
||||
(plain, html)
|
||||
}
|
||||
|
||||
/// Format a rate limit warning notification message.
|
||||
///
|
||||
@@ -383,39 +359,13 @@ pub fn spawn_notification_listener(
|
||||
ref agent_name,
|
||||
reset_at,
|
||||
}) => {
|
||||
// Debounce: reuse the same key as RateLimitWarning so both
|
||||
// types are rate-limited together for the same agent.
|
||||
let debounce_key = format!("{story_id}:{agent_name}");
|
||||
let now = Instant::now();
|
||||
if let Some(&last) = rate_limit_last_notified.get(&debounce_key)
|
||||
&& now.duration_since(last) < RATE_LIMIT_DEBOUNCE
|
||||
{
|
||||
// Log server-side for debugging; do NOT post to Matrix.
|
||||
// Hard-block auto-resume is normal operation — the status
|
||||
// command already surfaces rate-limit state via emoji.
|
||||
slog!(
|
||||
"[bot] Rate-limit hard-block notification debounced for \
|
||||
{story_id}:{agent_name}"
|
||||
"[bot] Rate-limit hard block for {story_id}/{agent_name}, \
|
||||
auto-resume at {reset_at}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
rate_limit_last_notified.insert(debounce_key, now);
|
||||
|
||||
let story_name = find_story_name_any_stage(&project_root, story_id);
|
||||
let (plain, html) = format_rate_limit_hard_block_notification(
|
||||
story_id,
|
||||
story_name.as_deref(),
|
||||
agent_name,
|
||||
reset_at,
|
||||
);
|
||||
|
||||
slog!("[bot] Sending rate-limit hard-block notification: {plain}");
|
||||
|
||||
for room_id in &get_room_ids() {
|
||||
if let Err(e) = transport.send_message(room_id, &plain, &html).await {
|
||||
slog!(
|
||||
"[bot] Failed to send rate-limit hard-block notification \
|
||||
to {room_id}: {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(WatcherEvent::ConfigChanged) => {
|
||||
// Hot-reload: pick up any changes to rate_limit_notifications.
|
||||
@@ -1062,17 +1012,10 @@ mod tests {
|
||||
assert_eq!(calls.len(), 0, "RateLimitWarning should be suppressed when rate_limit_notifications = false");
|
||||
}
|
||||
|
||||
/// AC3: RateLimitHardBlock is always sent regardless of rate_limit_notifications.
|
||||
/// RateLimitHardBlock is never posted to Matrix — it is logged server-side only.
|
||||
#[tokio::test]
|
||||
async fn rate_limit_hard_block_always_sent_when_config_false() {
|
||||
async fn rate_limit_hard_block_never_sent_to_matrix() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk_dir = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk_dir).unwrap();
|
||||
std::fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
"rate_limit_notifications = false\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (watcher_tx, watcher_rx) = broadcast::channel::<WatcherEvent>(16);
|
||||
let (transport, calls) = MockTransport::new();
|
||||
@@ -1094,7 +1037,7 @@ mod tests {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
let calls = calls.lock().unwrap();
|
||||
assert_eq!(calls.len(), 1, "RateLimitHardBlock should always be sent");
|
||||
assert_eq!(calls.len(), 0, "RateLimitHardBlock must not post to Matrix");
|
||||
}
|
||||
|
||||
/// AC3: StoryBlocked is always sent regardless of rate_limit_notifications.
|
||||
|
||||
Reference in New Issue
Block a user