From 2b9aba0e5dd661e597967ebb2010b2ef026c1baa Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Sun, 5 Apr 2026 16:18:10 -0400 Subject: [PATCH] fix shutdown hang and slow startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/mind/mod.rs | 7 +++++-- src/user/event_loop.rs | 34 +++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/mind/mod.rs b/src/mind/mod.rs index 751cc05..902bdd3 100644 --- a/src/mind/mod.rs +++ b/src/mind/mod.rs @@ -361,8 +361,11 @@ impl Mind { tokio::select! { biased; - Some(cmd) = input_rx.recv() => { - cmds.push(cmd); + cmd = input_rx.recv() => { + match cmd { + Some(cmd) => cmds.push(cmd), + None => break, // UI shut down + } } Some(bg) = bg_rx.recv() => { diff --git a/src/user/event_loop.rs b/src/user/event_loop.rs index 34d789c..fe8f309 100644 --- a/src/user/event_loop.rs +++ b/src/user/event_loop.rs @@ -287,22 +287,15 @@ pub async fn run( terminal.hide_cursor()?; - // Startup info 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); terminal.draw(|f| app.draw(f))?; + // Replay conversation after Mind init completes (non-blocking check) + let mut startup_done = false; + loop { tokio::select! { biased; @@ -342,6 +335,19 @@ pub async fn run( idle_state.decay_ewma(); 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 { let cur = shared_mind.lock().unwrap(); @@ -444,9 +450,11 @@ pub async fn run( } _ => { 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); - prev_mind = s.clone(); + drop(s); + diff_mind_state(&n, &prev_mind, &ui_tx, &mut dirty); + prev_mind = n; } } }