use crate::agent::cmd::log;
use crate::agent::launch::log_resolved_agent_launch;
use crate::agent::process::stop_requested;
use crate::agent::run::run_agent;
use crate::agent::types::{AgentEvent, Config, EVENT_SENDER, Workflow};
use std::sync::Mutex;
#[derive(Clone, Debug)]
struct ChatTurn {
is_user: bool,
content: String,
}
static CHAT_HISTORY: Mutex<Vec<ChatTurn>> = Mutex::new(Vec::new());
pub fn reset_chat_history() {
if let Ok(mut history) = CHAT_HISTORY.lock() {
history.clear();
}
}
fn build_chat_prompt(project_name: &str, history: &[ChatTurn], new_message: &str) -> String {
let mut prompt = format!(
"You are a helpful assistant for the \"{project_name}\" project. \
The user is chatting with you freely — there is no specific workflow or task. \
Answer questions, discuss ideas, explain code, suggest improvements, \
or help with whatever the user needs. Be concise and direct.\n\n"
);
for turn in history {
if turn.is_user {
prompt.push_str(&format!("User: {}\n\n", turn.content));
} else {
prompt.push_str(&format!("Assistant: {}\n\n", turn.content));
}
}
prompt.push_str(&format!("User: {new_message}\n\nAssistant:"));
prompt
}
pub fn run_chat_send(cfg: &Config, message: &str) {
if let Ok(mut history) = CHAT_HISTORY.lock() {
history.push(ChatTurn {
is_user: true,
content: message.to_string(),
});
}
let history_snapshot = CHAT_HISTORY.lock().map(|h| h.clone()).unwrap_or_default();
let prompt = build_chat_prompt(
&cfg.project_name,
&history_snapshot[..history_snapshot.len() - 1],
message,
);
if cfg.dry_run {
log_resolved_agent_launch(cfg, &[]);
log("[dry-run] Would send chat message");
if let Some(tx) = EVENT_SENDER.get() {
let _ = tx.send(AgentEvent::Done);
}
return;
}
run_agent(cfg, &prompt);
if stop_requested() {
log("Stop requested. Chat cancelled.");
if let Some(tx) = EVENT_SENDER.get() {
let _ = tx.send(AgentEvent::Done);
}
return;
}
if let Some(tx) = EVENT_SENDER.get() {
let _ = tx.send(AgentEvent::AwaitingFeedback(Workflow::Chat));
}
}
pub fn record_agent_response(text: &str) {
if let Ok(mut history) = CHAT_HISTORY.lock() {
history.push(ChatTurn {
is_user: false,
content: text.to_string(),
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chat_history_accumulates() {
reset_chat_history();
{
let mut guard = CHAT_HISTORY.lock().unwrap();
guard.push(ChatTurn {
is_user: true,
content: "hello".into(),
});
guard.push(ChatTurn {
is_user: false,
content: "hi there".into(),
});
}
let current = CHAT_HISTORY.lock().unwrap().clone();
assert_eq!(current.len(), 2);
assert!(current[0].is_user);
assert!(!current[1].is_user);
reset_chat_history();
}
#[test]
fn build_chat_prompt_includes_history() {
let history = vec![
ChatTurn {
is_user: true,
content: "What is this project?".into(),
},
ChatTurn {
is_user: false,
content: "It is a dev agent framework.".into(),
},
];
let prompt = build_chat_prompt("my-project", &history, "Tell me more");
assert!(prompt.contains("my-project"));
assert!(prompt.contains("What is this project?"));
assert!(prompt.contains("It is a dev agent framework."));
assert!(prompt.contains("Tell me more"));
}
}