mind: add turn_watch, move /retry to event_loop

Add tokio::sync::watch for turn_in_progress state. Commands in the
UI event loop can wait for turns to complete via wait_for() instead
of checking-and-bailing.

Move /retry to event_loop: waits for turn completion, pops agent
history, sends retried text as MindMessage::UserInput. Mind doesn't
need to know about retry — it just sees a new input message.

Make agent field pub on Mind for UI access.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 02:37:51 -04:00
parent 178824fa01
commit b05c956ab8
2 changed files with 63 additions and 36 deletions

View file

@ -52,6 +52,34 @@ fn send_help(ui_tx: &ui_channel::UiSender) {
));
}
async fn cmd_retry(
agent: &Arc<Mutex<Agent>>,
ui_tx: &ui_channel::UiSender,
mind_tx: &tokio::sync::mpsc::UnboundedSender<MindMessage>,
) {
let mut agent_guard = agent.lock().await;
let entries = agent_guard.entries_mut();
let mut last_user_text = None;
while let Some(entry) = entries.last() {
if entry.message().role == crate::agent::api::types::Role::User {
last_user_text = Some(entries.pop().unwrap().message().content_text().to_string());
break;
}
entries.pop();
}
drop(agent_guard);
match last_user_text {
Some(text) => {
let preview_len = text.len().min(60);
let _ = ui_tx.send(UiMessage::Info(format!("(retrying: {}...)", &text[..preview_len])));
let _ = mind_tx.send(MindMessage::UserInput(text));
}
None => {
let _ = ui_tx.send(UiMessage::Info("(nothing to retry)".into()));
}
}
}
pub async fn cmd_switch_model(
agent: &Arc<Mutex<Agent>>,
name: &str,
@ -94,6 +122,7 @@ pub async fn cmd_switch_model(
pub async fn run(
mut app: tui::App,
agent: Arc<Mutex<Agent>>,
turn_watch: tokio::sync::watch::Receiver<bool>,
mind_tx: tokio::sync::mpsc::UnboundedSender<MindMessage>,
ui_tx: ui_channel::UiSender,
mut ui_rx: ui_channel::UiReceiver,
@ -203,6 +232,17 @@ pub async fn run(
let _ = ui_tx.send(UiMessage::Info("(busy)".into()));
}
}
"/retry" => {
let agent = agent.clone();
let ui_tx = ui_tx.clone();
let mind_tx = mind_tx.clone();
let mut tw = turn_watch.clone();
tokio::spawn(async move {
// Wait for any in-progress turn to complete
let _ = tw.wait_for(|&active| !active).await;
cmd_retry(&agent, &ui_tx, &mind_tx).await;
});
}
cmd if cmd.starts_with("/model ") => {
let name = cmd[7..].trim().to_string();
if name.is_empty() {