event_loop: run() takes &Mind instead of individual handles

Simplifies the interface — run() receives one reference to Mind
and extracts agent, shared, turn_watch locally. Reduces parameter
count from 7 to 5.

Also: command table data structure (SlashCommand) and commands()
function for single source of truth. send_help uses it. Dispatch
refactor to follow.

Also: fix input submission — diff before push, clone after push,
so prev_mind captures the input for consumption detection.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 16:53:05 -04:00
parent 2b9aba0e5d
commit 3c4220c079

View file

@ -18,6 +18,29 @@ use crate::user::ui_channel::{self, UiMessage};
pub use crate::mind::MindCommand; pub use crate::mind::MindCommand;
// ── Slash commands ─────────────────────────────────────────────
struct SlashCommand {
name: &'static str,
help: &'static str,
}
fn commands() -> Vec<SlashCommand> {
vec![
SlashCommand { name: "/quit", help: "Exit consciousness" },
SlashCommand { name: "/new", help: "Start fresh session (saves current)" },
SlashCommand { name: "/save", help: "Save session to disk" },
SlashCommand { name: "/retry", help: "Re-run last turn" },
SlashCommand { name: "/model", help: "Show/switch model (/model <name>)" },
SlashCommand { name: "/score", help: "Score memory importance" },
SlashCommand { name: "/dmn", help: "Show DMN state" },
SlashCommand { name: "/sleep", help: "Put DMN to sleep" },
SlashCommand { name: "/wake", help: "Wake DMN to foraging" },
SlashCommand { name: "/pause", help: "Full stop — no autonomous ticks (Ctrl+P)" },
SlashCommand { name: "/help", help: "Show this help" },
]
}
/// Top-level entry point — creates Mind and UI, wires them together. /// Top-level entry point — creates Mind and UI, wires them together.
pub async fn start(cli: crate::user::CliArgs) -> Result<()> { pub async fn start(cli: crate::user::CliArgs) -> Result<()> {
let (config, _figment) = crate::config::load_session(&cli)?; let (config, _figment) = crate::config::load_session(&cli)?;
@ -34,7 +57,6 @@ pub async fn start(cli: crate::user::CliArgs) -> Result<()> {
let shared_context = mind.agent.lock().await.shared_context.clone(); let shared_context = mind.agent.lock().await.shared_context.clone();
let shared_active_tools = mind.agent.lock().await.active_tools.clone(); let shared_active_tools = mind.agent.lock().await.active_tools.clone();
let turn_watch = mind.turn_watch();
let mut result = Ok(()); let mut result = Ok(());
tokio_scoped::scope(|s| { tokio_scoped::scope(|s| {
@ -48,7 +70,7 @@ pub async fn start(cli: crate::user::CliArgs) -> Result<()> {
s.spawn(async { s.spawn(async {
result = run( result = run(
tui::App::new(String::new(), shared_context, shared_active_tools), tui::App::new(String::new(), shared_context, shared_active_tools),
&mind.agent, &mind.shared, turn_watch, mind_tx, ui_tx, ui_rx, &mind, mind_tx, ui_tx, ui_rx,
).await; ).await;
}); });
}); });
@ -56,21 +78,8 @@ pub async fn start(cli: crate::user::CliArgs) -> Result<()> {
} }
fn send_help(ui_tx: &ui_channel::UiSender) { fn send_help(ui_tx: &ui_channel::UiSender) {
let commands = &[ for cmd in &commands() {
("/quit", "Exit consciousness"), let _ = ui_tx.send(UiMessage::Info(format!(" {:12} {}", cmd.name, cmd.help)));
("/new", "Start fresh session (saves current)"),
("/save", "Save session to disk"),
("/retry", "Re-run last turn"),
("/model", "Show/switch model (/model <name>)"),
("/score", "Score memory importance"),
("/dmn", "Show DMN state"),
("/sleep", "Put DMN to sleep"),
("/wake", "Wake DMN to foraging"),
("/pause", "Full stop — no autonomous ticks (Ctrl+P)"),
("/help", "Show this help"),
];
for (name, desc) in commands {
let _ = ui_tx.send(UiMessage::Info(format!(" {:12} {}", name, desc)));
} }
let _ = ui_tx.send(UiMessage::Info(String::new())); let _ = ui_tx.send(UiMessage::Info(String::new()));
let _ = ui_tx.send(UiMessage::Info( let _ = ui_tx.send(UiMessage::Info(
@ -257,13 +266,14 @@ fn diff_mind_state(
pub async fn run( pub async fn run(
mut app: tui::App, mut app: tui::App,
agent: &Arc<Mutex<Agent>>, mind: &crate::mind::Mind,
shared_mind: &crate::mind::SharedMindState,
turn_watch: tokio::sync::watch::Receiver<bool>,
mind_tx: tokio::sync::mpsc::UnboundedSender<MindCommand>, mind_tx: tokio::sync::mpsc::UnboundedSender<MindCommand>,
ui_tx: ui_channel::UiSender, ui_tx: ui_channel::UiSender,
mut ui_rx: ui_channel::UiReceiver, mut ui_rx: ui_channel::UiReceiver,
) -> Result<()> { ) -> Result<()> {
let agent = &mind.agent;
let shared_mind = &mind.shared;
let turn_watch = mind.turn_watch();
// UI-owned state // UI-owned state
let mut idle_state = crate::thalamus::idle::State::new(); let mut idle_state = crate::thalamus::idle::State::new();
idle_state.load(); idle_state.load();
@ -450,11 +460,9 @@ pub async fn run(
} }
_ => { _ => {
let mut s = shared_mind.lock().unwrap(); let mut s = shared_mind.lock().unwrap();
let n = s.clone(); diff_mind_state(&s, &prev_mind, &ui_tx, &mut dirty);
s.input.push(input); s.input.push(input);
drop(s); prev_mind = s.clone();
diff_mind_state(&n, &prev_mind, &ui_tx, &mut dirty);
prev_mind = n;
} }
} }
} }