tycode_core/modules/memory/
command.rs1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use chrono::Utc;
5
6use crate::agents::agent::ActiveAgent;
7use crate::agents::memory_summarizer::MemorySummarizerAgent;
8use crate::agents::runner::AgentRunner;
9use crate::ai::Message;
10use crate::chat::actor::ActorState;
11use crate::chat::events::{ChatMessage, MessageSender};
12use crate::module::SlashCommand;
13use crate::spawn::complete_task::CompleteTask;
14use crate::tools::r#trait::ToolExecutor;
15
16use super::compaction::{self, CompactionStore};
17
18pub struct MemorySlashCommand;
19
20#[async_trait::async_trait(?Send)]
21impl SlashCommand for MemorySlashCommand {
22 fn name(&self) -> &'static str {
23 "memory"
24 }
25
26 fn description(&self) -> &'static str {
27 "Manage memories (summarize, compact)"
28 }
29
30 fn usage(&self) -> &'static str {
31 "/memory <summarize|compact|show>"
32 }
33
34 async fn execute(&self, state: &mut ActorState, args: &[&str]) -> Vec<ChatMessage> {
35 if args.is_empty() {
36 return vec![create_message(
37 "Usage: /memory <summarize|compact|show>".to_string(),
38 MessageSender::System,
39 )];
40 }
41
42 match args[0] {
43 "summarize" => handle_memory_summarize_command(state).await,
44 "compact" => handle_memory_compact_command(state).await,
45 "show" => handle_memory_show_command(state),
46 _ => vec![create_message(
47 format!(
48 "Unknown memory subcommand: {}. Use: summarize, compact, show",
49 args[0]
50 ),
51 MessageSender::Error,
52 )],
53 }
54 }
55}
56
57fn create_message(content: String, sender: MessageSender) -> ChatMessage {
58 ChatMessage {
59 content,
60 sender,
61 timestamp: Utc::now().timestamp_millis() as u64,
62 reasoning: None,
63 tool_calls: Vec::new(),
64 model_info: None,
65 token_usage: None,
66 images: vec![],
67 }
68}
69
70async fn handle_memory_summarize_command(state: &mut ActorState) -> Vec<ChatMessage> {
71 let memories = match state.memory_log.read_all() {
72 Ok(m) => m,
73 Err(e) => {
74 return vec![create_message(
75 format!("Failed to read memories: {e:?}"),
76 MessageSender::Error,
77 )];
78 }
79 };
80
81 if memories.is_empty() {
82 return vec![create_message(
83 "No memories to summarize.".to_string(),
84 MessageSender::System,
85 )];
86 }
87
88 let mut formatted = String::from("# Memories to Summarize\n\n");
89 for memory in &memories {
90 formatted.push_str(&format!(
91 "## Memory #{} ({})\n",
92 memory.seq,
93 memory.source.as_deref().unwrap_or("global")
94 ));
95 formatted.push_str(&memory.content);
96 formatted.push_str("\n\n");
97 }
98
99 let memory_count = memories.len();
100 state.event_sender.send_message(ChatMessage::system(format!(
101 "Summarizing {} memories...",
102 memory_count
103 )));
104
105 let mut tools: BTreeMap<String, Arc<dyn ToolExecutor + Send + Sync>> = BTreeMap::new();
106 tools.insert(
107 CompleteTask::tool_name().to_string(),
108 Arc::new(CompleteTask::standalone()),
109 );
110
111 let runner = AgentRunner::new(
112 state.provider.clone(),
113 state.settings.clone(),
114 tools,
115 state.modules.clone(),
116 state.steering.clone(),
117 state.prompt_builder.clone(),
118 state.context_builder.clone(),
119 );
120 let agent = MemorySummarizerAgent::new();
121 let mut active_agent = ActiveAgent::new(Arc::new(agent));
122 active_agent.conversation.push(Message::user(formatted));
123
124 match runner.run(active_agent, 10).await {
125 Ok(result) => vec![create_message(
126 format!("=== Memory Summary ===\n\n{}", result),
127 MessageSender::System,
128 )],
129 Err(e) => vec![create_message(
130 format!("Memory summarization failed: {e:?}"),
131 MessageSender::Error,
132 )],
133 }
134}
135
136async fn handle_memory_compact_command(state: &mut ActorState) -> Vec<ChatMessage> {
137 let count = match compaction::memories_since_last_compaction(&state.memory_log) {
138 Ok(c) => c,
139 Err(e) => {
140 return vec![create_message(
141 format!("Failed to check memories: {e:?}"),
142 MessageSender::Error,
143 )];
144 }
145 };
146
147 if count == 0 {
148 return vec![create_message(
149 "No new memories since last compaction.".to_string(),
150 MessageSender::System,
151 )];
152 }
153
154 state.event_sender.send_message(ChatMessage::system(format!(
155 "Compacting {count} new memories..."
156 )));
157
158 match compaction::run_compaction(
159 &state.memory_log,
160 state.provider.clone(),
161 state.settings.clone(),
162 state.modules.clone(),
163 state.steering.clone(),
164 state.prompt_builder.clone(),
165 state.context_builder.clone(),
166 )
167 .await
168 {
169 Ok(Some(c)) => vec![create_message(
170 format!(
171 "=== Compaction Complete ===\n\n\
172 Compacted {} memories through seq #{}.\n\
173 Saved to: compaction_{}.json\n\n\
174 Summary:\n{}",
175 c.memories_count, c.through_seq, c.through_seq, c.summary
176 ),
177 MessageSender::System,
178 )],
179 Ok(None) => vec![create_message(
180 "No new memories to compact.".to_string(),
181 MessageSender::System,
182 )],
183 Err(e) => vec![create_message(
184 format!("Memory compaction failed: {e:?}"),
185 MessageSender::Error,
186 )],
187 }
188}
189
190fn handle_memory_show_command(state: &mut ActorState) -> Vec<ChatMessage> {
191 let memory_dir = match state.memory_log.path().parent() {
192 Some(dir) => dir.to_path_buf(),
193 None => {
194 return vec![create_message(
195 "Failed to get memory directory".to_string(),
196 MessageSender::Error,
197 )];
198 }
199 };
200 let compaction_store = CompactionStore::new(memory_dir);
201
202 let latest = match compaction_store.find_latest() {
203 Ok(Some(c)) => c,
204 Ok(None) => {
205 return vec![create_message(
206 "No compaction exists yet. Run /memory compact to create one.".to_string(),
207 MessageSender::System,
208 )];
209 }
210 Err(e) => {
211 return vec![create_message(
212 format!("Failed to read compaction: {e:?}"),
213 MessageSender::Error,
214 )];
215 }
216 };
217
218 let pending = match compaction::memories_since_last_compaction(&state.memory_log) {
219 Ok(c) => c,
220 Err(e) => {
221 return vec![create_message(
222 format!("Failed to count pending memories: {e:?}"),
223 MessageSender::Error,
224 )];
225 }
226 };
227
228 vec![create_message(
229 format!(
230 "=== Current Memory Compaction ===\n\n\
231 Through seq: #{}\n\
232 Memories compacted: {}\n\
233 Created: {}\n\
234 Pending (uncompacted): {}\n\n\
235 ---\n\n{}",
236 latest.through_seq,
237 latest.memories_count,
238 latest.created_at.format("%Y-%m-%d %H:%M:%S UTC"),
239 pending,
240 latest.summary
241 ),
242 MessageSender::System,
243 )]
244}