agent: switch from tokio::sync::Mutex to std::sync::Mutex

The agent lock is never held across await points — turns lock briefly,
do work, drop, then do async API calls. std::sync::Mutex works and
can be locked from sync contexts (screen tick inside terminal.draw).

Fixes: blocking_lock() panic when called inside tokio runtime.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 19:56:56 -04:00
parent f29b4be09c
commit 3e1be4d353
8 changed files with 41 additions and 39 deletions

View file

@ -9,7 +9,7 @@ use ratatui::crossterm::event::{Event, EventStream, KeyEventKind};
use futures::StreamExt;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use std::sync::Mutex;
use crate::agent::Agent;
use crate::agent::api::ApiClient;
@ -116,8 +116,8 @@ pub async fn start(cli: crate::user::CliArgs) -> Result<()> {
let mind = crate::mind::Mind::new(config, ui_tx.clone(), turn_tx);
let shared_context = mind.agent.lock().await.shared_context.clone();
let shared_active_tools = mind.agent.lock().await.active_tools.clone();
let shared_context = mind.agent.lock().unwrap().shared_context.clone();
let shared_active_tools = mind.agent.lock().unwrap().active_tools.clone();
let mut result = Ok(());
tokio_scoped::scope(|s| {
@ -164,7 +164,7 @@ async fn cmd_retry_inner(
mind_tx: &tokio::sync::mpsc::UnboundedSender<MindCommand>,
ui_tx: &ui_channel::UiSender,
) {
let mut agent_guard = agent.lock().await;
let mut agent_guard = agent.lock().unwrap();
let entries = agent_guard.entries_mut();
let mut last_user_text = None;
while let Some(entry) = entries.last() {
@ -211,7 +211,7 @@ fn hotkey_cycle_reasoning(mind: &crate::mind::Mind, ui_tx: &ui_channel::UiSender
}
async fn hotkey_kill_processes(mind: &crate::mind::Mind, ui_tx: &ui_channel::UiSender) {
let active_tools = mind.agent.lock().await.active_tools.clone();
let active_tools = mind.agent.lock().unwrap().active_tools.clone();
let mut tools = active_tools.lock().unwrap();
if tools.is_empty() {
let _ = ui_tx.send(UiMessage::Info("(no running tool calls)".into()));
@ -284,7 +284,7 @@ pub async fn cmd_switch_model(
ui_tx: &ui_channel::UiSender,
) {
let resolved = {
let ag = agent.lock().await;
let ag = agent.lock().unwrap();
match ag.app_config.resolve_model(name) {
Ok(r) => r,
Err(e) => {
@ -296,11 +296,11 @@ pub async fn cmd_switch_model(
let new_client = ApiClient::new(&resolved.api_base, &resolved.api_key, &resolved.model_id);
let prompt_changed = {
let ag = agent.lock().await;
let ag = agent.lock().unwrap();
resolved.prompt_file != ag.prompt_file
};
let mut ag = agent.lock().await;
let mut ag = agent.lock().unwrap();
ag.swap_client(new_client);
if prompt_changed {