mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
fix(summon): restore skill supporting files and directory path in load output (#7457)
This commit is contained in:
@@ -45,6 +45,7 @@ pub struct Source {
|
||||
pub description: String,
|
||||
pub path: PathBuf,
|
||||
pub content: String,
|
||||
pub supporting_files: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -174,6 +175,7 @@ fn parse_skill_content(content: &str, path: PathBuf) -> Option<Source> {
|
||||
description: metadata.description,
|
||||
path,
|
||||
content: body,
|
||||
supporting_files: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -195,9 +197,36 @@ fn parse_agent_content(content: &str, path: PathBuf) -> Option<Source> {
|
||||
description,
|
||||
path,
|
||||
content: body,
|
||||
supporting_files: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Collect all files in a skill directory (excluding SKILL.md itself),
|
||||
/// recursing one level into subdirectories.
|
||||
fn find_supporting_files(directory: &Path, skill_file: &Path) -> Vec<PathBuf> {
|
||||
let mut files = Vec::new();
|
||||
let entries = match std::fs::read_dir(directory) {
|
||||
Ok(e) => e,
|
||||
Err(_) => return files,
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file() && path != skill_file {
|
||||
files.push(path);
|
||||
} else if path.is_dir() {
|
||||
if let Ok(sub_entries) = std::fs::read_dir(&path) {
|
||||
for sub_entry in sub_entries.flatten() {
|
||||
let sub_path = sub_entry.path();
|
||||
if sub_path.is_file() {
|
||||
files.push(sub_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
files
|
||||
}
|
||||
|
||||
fn round_duration(d: Duration) -> String {
|
||||
let secs = d.as_secs();
|
||||
if secs < 60 {
|
||||
@@ -614,6 +643,7 @@ impl SummonClient {
|
||||
description,
|
||||
path: PathBuf::from(&sr.path),
|
||||
content: String::new(),
|
||||
supporting_files: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -684,6 +714,7 @@ impl SummonClient {
|
||||
description: recipe.description.clone(),
|
||||
path: path.clone(),
|
||||
content: recipe.instructions.clone().unwrap_or_default(),
|
||||
supporting_files: Vec::new(),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -723,8 +754,9 @@ impl SummonClient {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(source) = parse_skill_content(&content, skill_file) {
|
||||
if let Some(mut source) = parse_skill_content(&content, skill_dir.clone()) {
|
||||
if !seen.contains(&source.name) {
|
||||
source.supporting_files = find_supporting_files(&skill_dir, &skill_file);
|
||||
seen.insert(source.name.clone());
|
||||
sources.push(source);
|
||||
}
|
||||
@@ -1016,11 +1048,28 @@ impl SummonClient {
|
||||
Some(source) => {
|
||||
let content = source.to_load_text();
|
||||
|
||||
let output = format!(
|
||||
"# Loaded: {} ({})\n\n{}\n\n---\nThis knowledge is now available in your context.",
|
||||
let mut output = format!(
|
||||
"# Loaded: {} ({})\n\n{}\n",
|
||||
source.name, source.kind, content
|
||||
);
|
||||
|
||||
if !source.supporting_files.is_empty() {
|
||||
output.push_str(&format!(
|
||||
"\n## Supporting Files\n\nSkill directory: {}\n\nThe following supporting files are available:\n",
|
||||
source.path.display()
|
||||
));
|
||||
for file in &source.supporting_files {
|
||||
if let Ok(relative) = file.strip_prefix(&source.path) {
|
||||
output.push_str(&format!("- {}\n", relative.display()));
|
||||
}
|
||||
}
|
||||
output.push_str(
|
||||
"\nUse the file tools to read these files or run scripts as directed.\n",
|
||||
);
|
||||
}
|
||||
|
||||
output.push_str("\n---\nThis knowledge is now available in your context.");
|
||||
|
||||
Ok(vec![Content::text(output)])
|
||||
}
|
||||
None => {
|
||||
@@ -1837,6 +1886,37 @@ You review code."#;
|
||||
assert!(sources.iter().any(|s| s.kind == SourceKind::BuiltinSkill));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_skill_supporting_files_discovered() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
let skill_dir = temp_dir.path().join(".goose/skills/my-skill");
|
||||
fs::create_dir_all(&skill_dir).unwrap();
|
||||
fs::write(
|
||||
skill_dir.join("SKILL.md"),
|
||||
"---\nname: my-skill\ndescription: A skill with scripts\n---\nRun check_all.sh",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(skill_dir.join("myscript.sh"), "#!/bin/bash\necho ok").unwrap();
|
||||
fs::create_dir(skill_dir.join("templates")).unwrap();
|
||||
fs::write(skill_dir.join("templates/report.txt"), "template content").unwrap();
|
||||
|
||||
let client = SummonClient::new(create_test_context()).unwrap();
|
||||
let sources = client.discover_filesystem_sources(temp_dir.path());
|
||||
|
||||
let skill = sources.iter().find(|s| s.name == "my-skill").unwrap();
|
||||
assert_eq!(skill.path, skill_dir);
|
||||
assert_eq!(skill.supporting_files.len(), 2);
|
||||
|
||||
let file_names: Vec<String> = skill
|
||||
.supporting_files
|
||||
.iter()
|
||||
.filter_map(|f| f.file_name().map(|n| n.to_string_lossy().to_string()))
|
||||
.collect();
|
||||
assert!(file_names.contains(&"myscript.sh".to_string()));
|
||||
assert!(file_names.contains(&"report.txt".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_client_tools_and_unknown_tool() {
|
||||
let client = SummonClient::new(create_test_context()).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user