fix shutdown hang and slow startup

Mind::run() breaks when input_rx channel closes (UI shut down).
Previously the DMN timer kept the loop alive forever.

UI renders immediately without blocking on agent lock. Conversation
replay happens lazily on the render tick via try_lock — the UI
shows "consciousness v0.3" instantly, fills in model info and
conversation history once Mind init completes.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 16:18:10 -04:00
parent 71351574be
commit 2b9aba0e5d
2 changed files with 26 additions and 15 deletions

View file

@ -361,8 +361,11 @@ impl Mind {
tokio::select! { tokio::select! {
biased; biased;
Some(cmd) = input_rx.recv() => { cmd = input_rx.recv() => {
cmds.push(cmd); match cmd {
Some(cmd) => cmds.push(cmd),
None => break, // UI shut down
}
} }
Some(bg) = bg_rx.recv() => { Some(bg) = bg_rx.recv() => {

View file

@ -287,22 +287,15 @@ pub async fn run(
terminal.hide_cursor()?; terminal.hide_cursor()?;
// Startup info
let _ = ui_tx.send(UiMessage::Info("consciousness v0.3 (tui)".into())); let _ = ui_tx.send(UiMessage::Info("consciousness v0.3 (tui)".into()));
{
let ag = agent.lock().await;
let _ = ui_tx.send(UiMessage::Info(format!(" model: {}", ag.model())));
// Replay restored conversation to UI
if !ag.entries().is_empty() {
ui_channel::replay_session_to_ui(ag.entries(), &ui_tx);
let _ = ui_tx.send(UiMessage::Info("--- restored from conversation log ---".into()));
}
}
// Initial render // Initial render — don't wait for Mind to init
app.drain_messages(&mut ui_rx); app.drain_messages(&mut ui_rx);
terminal.draw(|f| app.draw(f))?; terminal.draw(|f| app.draw(f))?;
// Replay conversation after Mind init completes (non-blocking check)
let mut startup_done = false;
loop { loop {
tokio::select! { tokio::select! {
biased; biased;
@ -342,6 +335,19 @@ pub async fn run(
idle_state.decay_ewma(); idle_state.decay_ewma();
app.update_idle(&idle_state); app.update_idle(&idle_state);
// One-time: replay conversation after Mind init
if !startup_done {
if let Ok(ag) = agent.try_lock() {
if !ag.entries().is_empty() {
ui_channel::replay_session_to_ui(ag.entries(), &ui_tx);
let _ = ui_tx.send(UiMessage::Info("--- restored from conversation log ---".into()));
}
let _ = ui_tx.send(UiMessage::Info(format!(" model: {}", ag.model())));
startup_done = true;
dirty = true;
}
}
// Diff MindState — generate UI messages from changes // Diff MindState — generate UI messages from changes
{ {
let cur = shared_mind.lock().unwrap(); let cur = shared_mind.lock().unwrap();
@ -444,9 +450,11 @@ pub async fn run(
} }
_ => { _ => {
let mut s = shared_mind.lock().unwrap(); let mut s = shared_mind.lock().unwrap();
diff_mind_state(&s, &prev_mind, &ui_tx, &mut dirty); let n = s.clone();
s.input.push(input); s.input.push(input);
prev_mind = s.clone(); drop(s);
diff_mind_state(&n, &prev_mind, &ui_tx, &mut dirty);
prev_mind = n;
} }
} }
} }