feat(423): auto-schedule timer on rate limit to resume after reset

- pty.rs: detect rate_limit_event hard blocks, parse reset_at, emit
  WatcherEvent::RateLimitHardBlock with story_id, agent_name, reset_at
- watcher.rs: add RateLimitHardBlock variant to WatcherEvent enum
- timer.rs: add TimerStore::upsert (add-or-update-to-later) and
  spawn_rate_limit_auto_scheduler (listens for RateLimitHardBlock,
  upserts timer for the blocked story)
- notifications.rs: handle RateLimitHardBlock events with a debounced
  chat notification including the scheduled resume time;
  add format_rate_limit_hard_block_notification helper
- matrix/mod.rs: subscribe second watcher_rx for auto-scheduler,
  pass it to run_bot
- matrix/bot/run.rs: wire spawn_rate_limit_auto_scheduler into bot startup

Tests cover: AC1 (hard block detection in pty), AC2 (auto-scheduler
adds timer), AC3 (upsert deduplication), AC5 (chat notification sent),
AC6 (worktree preserved — timer fires start_agent on existing worktree)
This commit is contained in:
dave
2026-03-28 09:18:58 +00:00
parent 57407aed51
commit b44f3a33e3
6 changed files with 374 additions and 8 deletions
@@ -22,6 +22,7 @@ pub async fn run_bot(
config: super::super::config::BotConfig,
project_root: PathBuf,
watcher_rx: tokio::sync::broadcast::Receiver<crate::io::watcher::WatcherEvent>,
watcher_rx_auto: tokio::sync::broadcast::Receiver<crate::io::watcher::WatcherEvent>,
perm_rx: Arc<TokioMutex<mpsc::UnboundedReceiver<crate::http::context::PermissionForward>>>,
agents: Arc<AgentPool>,
shutdown_rx: watch::Receiver<Option<crate::rebuild::ShutdownReason>>,
@@ -224,6 +225,11 @@ pub async fn run_bot(
Arc::clone(&agents),
project_root.clone(),
);
// Auto-schedule timers when an agent hits a hard rate limit.
crate::chat::timer::spawn_rate_limit_auto_scheduler(
Arc::clone(&timer_store),
watcher_rx_auto,
);
let ctx = BotContext {
bot_user_id,