mind: move all slash commands to event_loop dispatch
All slash command routing now lives in user/event_loop.rs. Mind receives typed messages (NewSession, Score, DmnSleep, etc.) and handles them as named methods. No more handle_command() dispatch table or Command enum. Commands that only need Agent state (/model, /retry) run directly in the UI task. Commands that need Mind state (/new, /score, /dmn, /sleep, /wake, /pause) send a MindMessage. Mind is now purely: turn lifecycle, DMN state machine, and the named handlers for each message type. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
b05c956ab8
commit
64add58caa
2 changed files with 98 additions and 108 deletions
|
|
@ -35,11 +35,6 @@ fn compaction_threshold(app: &AppConfig) -> u32 {
|
||||||
(crate::agent::context::context_window() as u32) * app.compaction.hard_threshold_pct / 100
|
(crate::agent::context::context_window() as u32) * app.compaction.hard_threshold_pct / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of slash command handling.
|
|
||||||
pub enum Command {
|
|
||||||
Handled,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Mind: all mutable state for a running agent session ---
|
// --- Mind: all mutable state for a running agent session ---
|
||||||
|
|
||||||
|
|
@ -345,15 +340,7 @@ impl Mind {
|
||||||
self.spawn_turn(prompt, StreamTarget::Autonomous);
|
self.spawn_turn(prompt, StreamTarget::Autonomous);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle slash commands. Returns how the main loop should respond.
|
async fn cmd_new(&mut self) {
|
||||||
async fn handle_command(&mut self, input: &str) -> Command {
|
|
||||||
match input {
|
|
||||||
"/new" | "/clear" => {
|
|
||||||
if self.turn_in_progress {
|
|
||||||
let _ = self.ui_tx.send(UiMessage::Info("(turn in progress, please wait)".into()));
|
|
||||||
return Command::Handled;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let new_log = log::ConversationLog::new(
|
let new_log = log::ConversationLog::new(
|
||||||
self.config.session_dir.join("conversation.jsonl"),
|
self.config.session_dir.join("conversation.jsonl"),
|
||||||
).ok();
|
).ok();
|
||||||
|
|
@ -370,56 +357,50 @@ impl Mind {
|
||||||
shared_ctx,
|
shared_ctx,
|
||||||
shared_tools,
|
shared_tools,
|
||||||
);
|
);
|
||||||
}
|
drop(agent_guard);
|
||||||
self.dmn = dmn::State::Resting { since: Instant::now() };
|
self.dmn = dmn::State::Resting { since: Instant::now() };
|
||||||
let _ = self.ui_tx.send(UiMessage::Info("New session started.".into()));
|
let _ = self.ui_tx.send(UiMessage::Info("New session started.".into()));
|
||||||
Command::Handled
|
|
||||||
}
|
}
|
||||||
"/score" => {
|
|
||||||
|
fn cmd_score(&mut self) {
|
||||||
if self.scoring_in_flight {
|
if self.scoring_in_flight {
|
||||||
let _ = self.ui_tx.send(UiMessage::Info("(scoring already in progress)".into()));
|
let _ = self.ui_tx.send(UiMessage::Info("(scoring already in progress)".into()));
|
||||||
return Command::Handled;
|
return;
|
||||||
}
|
}
|
||||||
let (context, client) = {
|
|
||||||
let agent = self.agent.lock().await;
|
|
||||||
(agent.context.clone(), agent.client_clone())
|
|
||||||
};
|
|
||||||
self.scoring_in_flight = true;
|
|
||||||
let agent = self.agent.clone();
|
let agent = self.agent.clone();
|
||||||
let ui_tx = self.ui_tx.clone();
|
let ui_tx = self.ui_tx.clone();
|
||||||
|
self.scoring_in_flight = true;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = learn::score_memories(
|
let (context, client) = {
|
||||||
&context, &client, &ui_tx,
|
let ag = agent.lock().await;
|
||||||
).await;
|
(ag.context.clone(), ag.client_clone())
|
||||||
let agent = agent.lock().await;
|
};
|
||||||
|
let result = learn::score_memories(&context, &client, &ui_tx).await;
|
||||||
|
let ag = agent.lock().await;
|
||||||
match result {
|
match result {
|
||||||
Ok(scores) => {
|
Ok(scores) => ag.publish_context_state_with_scores(Some(&scores)),
|
||||||
agent.publish_context_state_with_scores(Some(&scores));
|
Err(e) => { let _ = ui_tx.send(UiMessage::Info(format!("[scoring failed: {:#}]", e))); }
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let _ = ui_tx.send(UiMessage::Info(format!("[scoring failed: {:#}]", e)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Command::Handled
|
|
||||||
}
|
}
|
||||||
"/dmn" => {
|
|
||||||
|
fn cmd_dmn_query(&self) {
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(format!("DMN state: {:?}", self.dmn)));
|
let _ = self.ui_tx.send(UiMessage::Info(format!("DMN state: {:?}", self.dmn)));
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(format!("Next tick in: {:?}", self.dmn.interval())));
|
let _ = self.ui_tx.send(UiMessage::Info(format!("Next tick in: {:?}", self.dmn.interval())));
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(format!(
|
let _ = self.ui_tx.send(UiMessage::Info(format!(
|
||||||
"Consecutive DMN turns: {}/{}", self.dmn_turns, self.max_dmn_turns,
|
"Consecutive DMN turns: {}/{}", self.dmn_turns, self.max_dmn_turns,
|
||||||
)));
|
)));
|
||||||
Command::Handled
|
|
||||||
}
|
}
|
||||||
"/sleep" => {
|
|
||||||
|
fn cmd_dmn_sleep(&mut self) {
|
||||||
self.dmn = dmn::State::Resting { since: Instant::now() };
|
self.dmn = dmn::State::Resting { since: Instant::now() };
|
||||||
self.dmn_turns = 0;
|
self.dmn_turns = 0;
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(
|
let _ = self.ui_tx.send(UiMessage::Info(
|
||||||
"DMN sleeping (heartbeat every 5 min). Type anything to wake.".into(),
|
"DMN sleeping (heartbeat every 5 min). Type anything to wake.".into(),
|
||||||
));
|
));
|
||||||
Command::Handled
|
|
||||||
}
|
}
|
||||||
"/wake" => {
|
|
||||||
|
fn cmd_dmn_wake(&mut self) {
|
||||||
let was_paused = matches!(self.dmn, dmn::State::Paused | dmn::State::Off);
|
let was_paused = matches!(self.dmn, dmn::State::Paused | dmn::State::Off);
|
||||||
if matches!(self.dmn, dmn::State::Off) {
|
if matches!(self.dmn, dmn::State::Off) {
|
||||||
dmn::set_off(false);
|
dmn::set_off(false);
|
||||||
|
|
@ -430,19 +411,15 @@ impl Mind {
|
||||||
else { "DMN waking — entering foraging mode." };
|
else { "DMN waking — entering foraging mode." };
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(msg.into()));
|
let _ = self.ui_tx.send(UiMessage::Info(msg.into()));
|
||||||
self.update_status();
|
self.update_status();
|
||||||
Command::Handled
|
|
||||||
}
|
}
|
||||||
"/pause" => {
|
|
||||||
|
fn cmd_dmn_pause(&mut self) {
|
||||||
self.dmn = dmn::State::Paused;
|
self.dmn = dmn::State::Paused;
|
||||||
self.dmn_turns = 0;
|
self.dmn_turns = 0;
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(
|
let _ = self.ui_tx.send(UiMessage::Info(
|
||||||
"DMN paused — no autonomous ticks. Ctrl+P or /wake to resume.".into(),
|
"DMN paused — no autonomous ticks. Ctrl+P or /wake to resume.".into(),
|
||||||
));
|
));
|
||||||
self.update_status();
|
self.update_status();
|
||||||
Command::Handled
|
|
||||||
}
|
|
||||||
_ => Command::None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interrupt: kill processes, abort current turn, clear pending queue.
|
/// Interrupt: kill processes, abort current turn, clear pending queue.
|
||||||
|
|
@ -598,12 +575,7 @@ impl Mind {
|
||||||
|
|
||||||
Some(msg) = input_rx.recv() => {
|
Some(msg) = input_rx.recv() => {
|
||||||
match msg {
|
match msg {
|
||||||
MindMessage::UserInput(input) => {
|
MindMessage::UserInput(input) => self.submit_input(input),
|
||||||
match self.handle_command(&input).await {
|
|
||||||
Command::Handled => {}
|
|
||||||
Command::None => self.submit_input(input),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MindMessage::Hotkey(action) => {
|
MindMessage::Hotkey(action) => {
|
||||||
match action {
|
match action {
|
||||||
HotkeyAction::CycleReasoning => self.cycle_reasoning(),
|
HotkeyAction::CycleReasoning => self.cycle_reasoning(),
|
||||||
|
|
@ -622,6 +594,12 @@ impl Mind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MindMessage::NewSession => self.cmd_new().await,
|
||||||
|
MindMessage::Score => self.cmd_score(),
|
||||||
|
MindMessage::DmnQuery => self.cmd_dmn_query(),
|
||||||
|
MindMessage::DmnSleep => self.cmd_dmn_sleep(),
|
||||||
|
MindMessage::DmnWake => self.cmd_dmn_wake(),
|
||||||
|
MindMessage::DmnPause => self.cmd_dmn_pause(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,12 @@ use crate::user::ui_channel::{self, UiMessage};
|
||||||
pub enum MindMessage {
|
pub enum MindMessage {
|
||||||
UserInput(String),
|
UserInput(String),
|
||||||
Hotkey(HotkeyAction),
|
Hotkey(HotkeyAction),
|
||||||
|
DmnSleep,
|
||||||
|
DmnWake,
|
||||||
|
DmnPause,
|
||||||
|
DmnQuery,
|
||||||
|
NewSession,
|
||||||
|
Score,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_help(ui_tx: &ui_channel::UiSender) {
|
fn send_help(ui_tx: &ui_channel::UiSender) {
|
||||||
|
|
@ -232,6 +238,12 @@ pub async fn run(
|
||||||
let _ = ui_tx.send(UiMessage::Info("(busy)".into()));
|
let _ = ui_tx.send(UiMessage::Info("(busy)".into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"/new" | "/clear" => { let _ = mind_tx.send(MindMessage::NewSession); }
|
||||||
|
"/dmn" => { let _ = mind_tx.send(MindMessage::DmnQuery); }
|
||||||
|
"/sleep" => { let _ = mind_tx.send(MindMessage::DmnSleep); }
|
||||||
|
"/wake" => { let _ = mind_tx.send(MindMessage::DmnWake); }
|
||||||
|
"/pause" => { let _ = mind_tx.send(MindMessage::DmnPause); }
|
||||||
|
"/score" => { let _ = mind_tx.send(MindMessage::Score); }
|
||||||
"/retry" => {
|
"/retry" => {
|
||||||
let agent = agent.clone();
|
let agent = agent.clone();
|
||||||
let ui_tx = ui_tx.clone();
|
let ui_tx = ui_tx.clone();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue