matrixcode_core/agent/context/
manager.rs1use std::path::PathBuf;
15
16use crate::prompt::PromptProfile;
17use crate::skills::Skill;
18
19pub struct AgentContext {
24 skills: Vec<Skill>,
26
27 project_overview: Option<String>,
29
30 memory_summary: Option<String>,
32
33 project_path: Option<PathBuf>,
35
36 system_prompt: String,
38
39 profile: PromptProfile,
41}
42
43impl AgentContext {
44 pub fn new(profile: PromptProfile) -> Self {
46 let system_prompt = crate::prompt::build_system_prompt(
47 &profile,
48 &[], None,
50 None,
51 );
52
53 Self {
54 skills: Vec::new(),
55 project_overview: None,
56 memory_summary: None,
57 project_path: None,
58 system_prompt,
59 profile,
60 }
61 }
62
63 pub fn with_context(
65 profile: PromptProfile,
66 skills: Vec<Skill>,
67 project_overview: Option<String>,
68 memory_summary: Option<String>,
69 project_path: Option<PathBuf>,
70 ) -> Self {
71 let system_prompt = crate::prompt::build_system_prompt(
72 &profile,
73 &skills,
74 project_overview.as_deref(),
75 memory_summary.as_deref(),
76 );
77
78 Self {
79 skills,
80 project_overview,
81 memory_summary,
82 project_path,
83 system_prompt,
84 profile,
85 }
86 }
87
88 pub fn skills(&self) -> &[Skill] {
90 &self.skills
91 }
92
93 pub fn set_skills(&mut self, skills: Vec<Skill>) {
95 self.skills = skills;
96 self.rebuild_system_prompt();
97 }
98
99 pub fn project_overview(&self) -> Option<&str> {
101 self.project_overview.as_deref()
102 }
103
104 pub fn set_project_overview(&mut self, overview: Option<String>) {
106 self.project_overview = overview;
107 self.rebuild_system_prompt();
108 }
109
110 pub fn memory_summary(&self) -> Option<&str> {
112 self.memory_summary.as_deref()
113 }
114
115 pub fn update_memory(&mut self, summary: Option<String>) {
117 self.memory_summary = summary;
118 self.rebuild_system_prompt();
119 }
120
121 pub fn project_path(&self) -> Option<&PathBuf> {
123 self.project_path.as_ref()
124 }
125
126 pub fn set_project_path(&mut self, path: Option<PathBuf>) {
128 self.project_path = path;
129 }
132
133 pub fn system_prompt(&self) -> &str {
135 &self.system_prompt
136 }
137
138 pub fn profile(&self) -> &PromptProfile {
140 &self.profile
141 }
142
143 fn rebuild_system_prompt(&mut self) {
145 self.system_prompt = crate::prompt::build_system_prompt(
146 &self.profile,
147 &self.skills,
148 self.project_overview.as_deref(),
149 self.memory_summary.as_deref(),
150 );
151 }
152
153 pub fn set_system_prompt(&mut self, prompt: String) {
155 self.system_prompt = prompt;
156 }
157
158 pub fn rebuild_system_prompt_with_workflows(&mut self, project_path: Option<PathBuf>) {
160 self.system_prompt = crate::prompt::build_system_prompt_with_workflows(
161 &self.profile,
162 &self.skills,
163 self.project_overview.as_deref(),
164 self.memory_summary.as_deref(),
165 project_path.as_ref(),
166 None, );
168 }
169
170 pub fn clear(&mut self) {
172 self.skills.clear();
173 self.project_overview = None;
174 self.memory_summary = None;
175 self.project_path = None;
176 self.rebuild_system_prompt();
177 }
178}
179
180impl Default for AgentContext {
181 fn default() -> Self {
182 Self::new(PromptProfile::default())
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_context_new_creates_empty_context() {
192 let context = AgentContext::new(PromptProfile::default());
193
194 assert_eq!(context.skills().len(), 0);
195 assert!(context.project_overview().is_none());
196 assert!(context.memory_summary().is_none());
197 assert!(context.project_path().is_none());
198 assert!(!context.system_prompt().is_empty(), "system prompt should not be empty");
199 }
200
201 #[test]
202 fn test_context_set_skills_updates_prompt() {
203 let mut context = AgentContext::new(PromptProfile::default());
204 let initial_prompt = context.system_prompt().to_string();
205
206 let skills = vec![Skill {
208 name: "test_skill".to_string(),
209 description: "Test skill description".to_string(),
210 trigger: Some("/test".to_string()),
211 skill_type: crate::skills::SkillType::Flexible,
212 priority: crate::skills::SkillPriority::Implementation,
213 mandatory: false,
214 dir: PathBuf::from("/skills/test_skill"),
215 body: "Test skill content".to_string(),
216 source_file: PathBuf::from("/skills/test_skill/SKILL.md"),
217 }];
218 context.set_skills(skills);
219
220 assert_ne!(context.system_prompt(), initial_prompt, "prompt should change when skills added");
222 assert!(context.system_prompt().contains("test_skill"), "prompt should include skill name");
223 assert!(context.system_prompt().contains("Test skill description"), "prompt should include skill description");
224 }
225
226 #[test]
227 fn test_context_update_memory_rebuilds_prompt() {
228 let mut context = AgentContext::new(PromptProfile::default());
229 let initial_prompt = context.system_prompt().to_string();
230
231 context.update_memory(Some("Previous conversation summary".to_string()));
233
234 assert_ne!(context.system_prompt(), initial_prompt, "prompt should change when memory updated");
236 assert!(context.system_prompt().contains("Previous conversation summary"), "prompt should include memory");
237 }
238
239 #[test]
240 fn test_context_set_project_overview() {
241 let mut context = AgentContext::new(PromptProfile::default());
242
243 context.set_project_overview(Some("Project overview text".to_string()));
244
245 assert_eq!(context.project_overview(), Some("Project overview text"));
246 }
247
248 #[test]
249 fn test_context_clear_resets_state() {
250 let mut context = AgentContext::new(PromptProfile::default());
251
252 context.set_skills(vec![Skill {
254 name: "skill".to_string(),
255 description: "description".to_string(),
256 trigger: Some("trigger".to_string()),
257 skill_type: crate::skills::SkillType::Flexible,
258 priority: crate::skills::SkillPriority::Implementation,
259 mandatory: false,
260 dir: PathBuf::from("/skills/skill"),
261 body: "content".to_string(),
262 source_file: PathBuf::from("/skills/skill/SKILL.md"),
263 }]);
264 context.set_project_overview(Some("overview".to_string()));
265 context.update_memory(Some("memory".to_string()));
266 context.set_project_path(Some(PathBuf::from("/path")));
267
268 context.clear();
270
271 assert_eq!(context.skills().len(), 0);
273 assert!(context.project_overview().is_none());
274 assert!(context.memory_summary().is_none());
275 assert!(context.project_path().is_none());
276 }
277
278 #[test]
279 fn test_context_with_context_creates_full_context() {
280 let skills = vec![Skill {
281 name: "skill".to_string(),
282 description: "skill description".to_string(),
283 trigger: Some("trigger".to_string()),
284 skill_type: crate::skills::SkillType::Flexible,
285 priority: crate::skills::SkillPriority::Implementation,
286 mandatory: false,
287 dir: PathBuf::from("/skills/skill"),
288 body: "skill content".to_string(),
289 source_file: PathBuf::from("/skills/skill/SKILL.md"),
290 }];
291 let overview = Some("Project overview".to_string());
292 let memory = Some("Memory summary".to_string());
293 let path = Some(PathBuf::from("/project"));
294
295 let context = AgentContext::with_context(
296 PromptProfile::default(),
297 skills.clone(),
298 overview.clone(),
299 memory.clone(),
300 path.clone(),
301 );
302
303 assert_eq!(context.skills(), &skills);
304 assert_eq!(context.project_overview(), overview.as_deref());
305 assert_eq!(context.memory_summary(), memory.as_deref());
306 assert_eq!(context.project_path(), path.as_ref());
307 }
308}