Skip to main content

tycode_core/modules/memory/
command.rs

1use 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}