feat: projects as backend sources with system prompt injection (#8739)
Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -366,6 +366,24 @@ impl Agent {
|
||||
messages
|
||||
}
|
||||
|
||||
async fn load_project_instructions(&self, session: &Session) -> Option<String> {
|
||||
let thread_id = session.thread_id.as_deref()?;
|
||||
let thread_mgr =
|
||||
crate::session::ThreadManager::new(self.config.session_manager.storage().clone());
|
||||
let thread = thread_mgr.get_thread(thread_id).await.ok()?;
|
||||
let project_id = thread.metadata.project_id.as_deref()?;
|
||||
let entry = crate::sources::read_project(project_id).ok()?;
|
||||
let mut parts = Vec::new();
|
||||
parts.push(format!("# Project: {}", entry.name));
|
||||
if !entry.description.is_empty() {
|
||||
parts.push(entry.description.clone());
|
||||
}
|
||||
if !entry.content.is_empty() {
|
||||
parts.push(entry.content.clone());
|
||||
}
|
||||
Some(parts.join("\n\n"))
|
||||
}
|
||||
|
||||
async fn prepare_reply_context(
|
||||
&self,
|
||||
session_id: &str,
|
||||
@@ -1251,6 +1269,11 @@ impl Agent {
|
||||
goose_mode,
|
||||
initial_messages,
|
||||
} = context;
|
||||
|
||||
if let Some(project_addendum) = self.load_project_instructions(&session).await {
|
||||
system_prompt = format!("{system_prompt}\n\n{project_addendum}");
|
||||
}
|
||||
|
||||
self.reset_retry_attempts().await;
|
||||
|
||||
let provider = self.provider().await?;
|
||||
|
||||
@@ -125,9 +125,10 @@ fn parse_agent_content(content: &str, path: &Path) -> Option<SourceEntry> {
|
||||
name: metadata.name,
|
||||
description,
|
||||
content: body,
|
||||
directory: path.to_string_lossy().into_owned(),
|
||||
path: path.to_string_lossy().into_owned(),
|
||||
global: false,
|
||||
supporting_files: Vec::new(),
|
||||
properties: std::collections::HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -171,9 +172,10 @@ fn scan_recipes_from_dir(
|
||||
name,
|
||||
description: recipe.description.clone(),
|
||||
content: recipe.instructions.clone().unwrap_or_default(),
|
||||
directory: path.to_string_lossy().into_owned(),
|
||||
path: path.to_string_lossy().into_owned(),
|
||||
global: false,
|
||||
supporting_files: Vec::new(),
|
||||
properties: std::collections::HashMap::new(),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -598,9 +600,10 @@ impl SummonClient {
|
||||
name: sr.name.clone(),
|
||||
description,
|
||||
content: String::new(),
|
||||
directory: sr.path.clone(),
|
||||
path: sr.path.clone(),
|
||||
global: false,
|
||||
supporting_files: Vec::new(),
|
||||
properties: std::collections::HashMap::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1160,7 +1163,7 @@ impl SummonClient {
|
||||
}
|
||||
}
|
||||
|
||||
let recipe_file = load_local_recipe_file(&source.directory)
|
||||
let recipe_file = load_local_recipe_file(&source.path)
|
||||
.map_err(|e| format!("Failed to load recipe '{}': {}", source.name, e))?;
|
||||
|
||||
let param_values: Vec<(String, String)> = params
|
||||
@@ -1193,10 +1196,10 @@ impl SummonClient {
|
||||
source: &SourceEntry,
|
||||
params: &DelegateParams,
|
||||
) -> Result<Recipe, String> {
|
||||
let agent_content = if source.directory.is_empty() {
|
||||
let agent_content = if source.path.is_empty() {
|
||||
return Err("Agent source has no path".to_string());
|
||||
} else {
|
||||
std::fs::read_to_string(&source.directory)
|
||||
std::fs::read_to_string(&source.path)
|
||||
.map_err(|e| format!("Failed to read agent file: {}", e))?
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user