telos_agent/agent/prompt/builtins/
context.rs1use async_trait::async_trait;
2use std::path::PathBuf;
3use std::sync::{Arc, Mutex};
4
5use crate::agent::prompt::{PromptSection, PromptStability};
6use crate::integrations::mcp::manager::McpManager;
7use crate::knowledge::memory::MemoryStatus;
8use crate::knowledge::memory::index::{MemoryQuery, MemorySort, MemoryStore};
9use crate::knowledge::memory::profile::ProfileManager;
10use crate::knowledge::skills::SkillRegistry;
11
12pub struct DateSection;
15
16#[async_trait]
17impl PromptSection for DateSection {
18 fn name(&self) -> &str {
19 "date"
20 }
21 fn stability(&self) -> PromptStability {
22 PromptStability::Static
24 }
25
26 async fn render(&self, _ctx: &()) -> String {
27 let date = time::OffsetDateTime::now_utc().date();
28 format!("Today's date is {}.", date)
29 }
30}
31
32pub struct CwdSection {
35 cwd: PathBuf,
36}
37
38impl CwdSection {
39 pub fn new(cwd: PathBuf) -> Self {
40 Self { cwd }
41 }
42}
43
44#[async_trait]
45impl PromptSection for CwdSection {
46 fn name(&self) -> &str {
47 "cwd"
48 }
49 fn stability(&self) -> PromptStability {
50 PromptStability::Static
52 }
53
54 async fn render(&self, _ctx: &()) -> String {
55 format!("Working directory: {}", self.cwd.display())
56 }
57}
58
59pub struct SkillsSection {
62 registry: Arc<SkillRegistry>,
63}
64
65impl SkillsSection {
66 pub fn new(registry: Arc<SkillRegistry>) -> Self {
67 Self { registry }
68 }
69}
70
71#[async_trait]
72impl PromptSection for SkillsSection {
73 fn name(&self) -> &str {
74 "skills"
75 }
76 fn stability(&self) -> PromptStability {
77 PromptStability::Static
78 }
79
80 async fn render(&self, _ctx: &()) -> String {
81 self.registry.render_for_prompt()
82 }
83}
84
85pub struct GitStatusSection;
88
89#[async_trait]
90impl PromptSection for GitStatusSection {
91 fn name(&self) -> &str {
92 "git_status"
93 }
94 fn stability(&self) -> PromptStability {
95 PromptStability::Static
97 }
98
99 async fn render(&self, _ctx: &()) -> String {
100 let mut command = std::process::Command::new("git");
101 command.args(["status", "--short"]);
102 hide_console_window(&mut command);
103 match command.output() {
104 Ok(output) if output.status.success() => {
105 let stdout = String::from_utf8_lossy(&output.stdout);
106 if stdout.trim().is_empty() {
107 "Git: clean working tree.".into()
108 } else {
109 format!("## Git Status\n```\n{}\n```", stdout.trim())
110 }
111 }
112 _ => String::new(),
113 }
114 }
115}
116
117#[cfg_attr(not(windows), allow(unused_variables))]
118fn hide_console_window(command: &mut std::process::Command) {
119 #[cfg(windows)]
120 {
121 use std::os::windows::process::CommandExt;
122
123 const CREATE_NO_WINDOW: u32 = 0x0800_0000;
124 command.creation_flags(CREATE_NO_WINDOW);
125 }
126}
127
128pub struct MemorySection {
131 store: Arc<Mutex<MemoryStore>>,
132}
133
134impl MemorySection {
135 pub fn new(store: Arc<Mutex<MemoryStore>>) -> Self {
136 Self { store }
137 }
138}
139
140#[async_trait]
141impl PromptSection for MemorySection {
142 fn name(&self) -> &str {
143 "memory"
144 }
145 fn stability(&self) -> PromptStability {
146 PromptStability::Static
149 }
150
151 async fn render(&self, _ctx: &()) -> String {
152 let store = self.store.clone();
153 match tokio::task::spawn_blocking(move || {
154 let store = store.lock().unwrap();
155 let mut memories = store.query(MemoryQuery {
156 limit: Some(8),
157 sort: MemorySort::Relevance,
158 ..MemoryQuery::default()
159 });
160 memories.retain(|entry| entry.status != MemoryStatus::Deprecated);
161 memories.truncate(5);
162 if memories.is_empty() {
163 return String::new();
164 }
165 let mut lines = vec!["## Relevant Memories".to_string()];
166 for entry in &memories {
167 lines.push(format!(
168 "- **{}** ({:?}, {:?}): {}",
169 entry.name, entry.category, entry.status, entry.description
170 ));
171 let preview: String = entry.body.chars().take(200).collect();
172 if !preview.is_empty() {
173 lines.push(format!(" {}", preview));
174 }
175 }
176 lines.join("\n")
177 })
178 .await
179 {
180 Ok(result) => result,
181 Err(e) => {
182 tracing::warn!("MemorySection::render failed: {e}");
183 String::new()
184 }
185 }
186 }
187}
188
189pub struct ProfileSection {
192 profile_manager: Arc<ProfileManager>,
193}
194
195impl ProfileSection {
196 pub fn new(profile_manager: Arc<ProfileManager>) -> Self {
197 Self { profile_manager }
198 }
199}
200
201#[async_trait]
202impl PromptSection for ProfileSection {
203 fn name(&self) -> &str {
204 "profile"
205 }
206 fn stability(&self) -> PromptStability {
207 PromptStability::Dynamic
208 }
209
210 async fn render(&self, _ctx: &()) -> String {
211 self.profile_manager.render_all()
212 }
213}
214
215pub struct McpSection {
219 manager: Arc<McpManager>,
220}
221
222impl McpSection {
223 pub fn new(manager: Arc<McpManager>) -> Self {
224 Self { manager }
225 }
226}
227
228#[async_trait]
229impl PromptSection for McpSection {
230 fn name(&self) -> &str {
231 "mcp"
232 }
233 fn stability(&self) -> PromptStability {
234 PromptStability::Dynamic
235 }
236
237 async fn render(&self, _ctx: &()) -> String {
238 let tools = self.manager.all_tools().await;
239 if tools.is_empty() {
240 return String::new();
241 }
242 let mut lines = vec!["## MCP Tools".to_string()];
243 for (server_id, tool) in &tools {
244 lines.push(format!("- **mcp__{}__{}**: {}", server_id, tool.name, tool.description));
245 }
246 lines.join("\n")
247 }
248}