huskies: merge 726_story_notify_chat_transports_when_oauth_account_swaps_or_all_accounts_are_exhausted

This commit is contained in:
dave
2026-04-27 18:39:35 +00:00
parent 80661fa622
commit 4b64bc614f
8 changed files with 187 additions and 4 deletions
+19 -2
View File
@@ -305,11 +305,17 @@ fn panic_payload_to_string(payload: &Box<dyn std::any::Any + Send>) -> String {
/// Spawn a background task that listens for [`WatcherEvent::RateLimitHardBlock`]
/// events and auto-schedules a timer for the blocked story.
///
/// When an OAuth account swap succeeds, emits [`WatcherEvent::OAuthAccountSwapped`]
/// so chat transports can notify the user which account took over. When all
/// accounts are exhausted, emits [`WatcherEvent::OAuthAccountsExhausted`] with
/// the earliest reset time.
///
/// If a timer already exists for the story, it is updated to the later reset time
/// rather than creating a duplicate (via [`TimerStore::upsert`]).
pub fn spawn_rate_limit_auto_scheduler(
store: Arc<TimerStore>,
mut watcher_rx: tokio::sync::broadcast::Receiver<crate::io::watcher::WatcherEvent>,
watcher_tx: tokio::sync::broadcast::Sender<crate::io::watcher::WatcherEvent>,
) {
tokio::spawn(async move {
loop {
@@ -344,6 +350,9 @@ pub fn spawn_rate_limit_auto_scheduler(
(agent {agent_name}): now using '{new_email}'. \
Auto-assign will restart the agent with the new account."
);
let _ = watcher_tx.send(
crate::io::watcher::WatcherEvent::OAuthAccountSwapped { new_email },
);
// No timer needed — auto-assign picks up the story.
continue;
}
@@ -352,6 +361,14 @@ pub fn spawn_rate_limit_auto_scheduler(
"[timer] Account swap not possible for story {story_id}: \
{swap_err}. Falling back to timer-based retry."
);
// Notify chat transports when all accounts are exhausted.
if swap_err.contains("All OAuth accounts are rate-limited") {
let _ = watcher_tx.send(
crate::io::watcher::WatcherEvent::OAuthAccountsExhausted {
earliest_reset_msg: swap_err.clone(),
},
);
}
}
}
@@ -561,7 +578,7 @@ mod tests {
let store = Arc::new(TimerStore::load(dir.path().join("timers.json")));
let (watcher_tx, watcher_rx) = tokio::sync::broadcast::channel::<WatcherEvent>(16);
spawn_rate_limit_auto_scheduler(Arc::clone(&store), watcher_rx);
spawn_rate_limit_auto_scheduler(Arc::clone(&store), watcher_rx, watcher_tx.clone());
let reset_at = Utc::now() + Duration::hours(1);
watcher_tx
@@ -591,7 +608,7 @@ mod tests {
let store = Arc::new(TimerStore::load(dir.path().join("timers.json")));
let (watcher_tx, watcher_rx) = tokio::sync::broadcast::channel::<WatcherEvent>(16);
spawn_rate_limit_auto_scheduler(Arc::clone(&store), watcher_rx);
spawn_rate_limit_auto_scheduler(Arc::clone(&store), watcher_rx, watcher_tx.clone());
let first = Utc::now() + Duration::hours(1);
let second = Utc::now() + Duration::hours(2);