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 description: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
pub supporting_files: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[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,
|
description: metadata.description,
|
||||||
path,
|
path,
|
||||||
content: body,
|
content: body,
|
||||||
|
supporting_files: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,9 +197,36 @@ fn parse_agent_content(content: &str, path: PathBuf) -> Option<Source> {
|
|||||||
description,
|
description,
|
||||||
path,
|
path,
|
||||||
content: body,
|
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 {
|
fn round_duration(d: Duration) -> String {
|
||||||
let secs = d.as_secs();
|
let secs = d.as_secs();
|
||||||
if secs < 60 {
|
if secs < 60 {
|
||||||
@@ -614,6 +643,7 @@ impl SummonClient {
|
|||||||
description,
|
description,
|
||||||
path: PathBuf::from(&sr.path),
|
path: PathBuf::from(&sr.path),
|
||||||
content: String::new(),
|
content: String::new(),
|
||||||
|
supporting_files: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -684,6 +714,7 @@ impl SummonClient {
|
|||||||
description: recipe.description.clone(),
|
description: recipe.description.clone(),
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
content: recipe.instructions.clone().unwrap_or_default(),
|
content: recipe.instructions.clone().unwrap_or_default(),
|
||||||
|
supporting_files: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
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) {
|
if !seen.contains(&source.name) {
|
||||||
|
source.supporting_files = find_supporting_files(&skill_dir, &skill_file);
|
||||||
seen.insert(source.name.clone());
|
seen.insert(source.name.clone());
|
||||||
sources.push(source);
|
sources.push(source);
|
||||||
}
|
}
|
||||||
@@ -1016,11 +1048,28 @@ impl SummonClient {
|
|||||||
Some(source) => {
|
Some(source) => {
|
||||||
let content = source.to_load_text();
|
let content = source.to_load_text();
|
||||||
|
|
||||||
let output = format!(
|
let mut output = format!(
|
||||||
"# Loaded: {} ({})\n\n{}\n\n---\nThis knowledge is now available in your context.",
|
"# Loaded: {} ({})\n\n{}\n",
|
||||||
source.name, source.kind, content
|
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)])
|
Ok(vec![Content::text(output)])
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -1837,6 +1886,37 @@ You review code."#;
|
|||||||
assert!(sources.iter().any(|s| s.kind == SourceKind::BuiltinSkill));
|
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]
|
#[tokio::test]
|
||||||
async fn test_client_tools_and_unknown_tool() {
|
async fn test_client_tools_and_unknown_tool() {
|
||||||
let client = SummonClient::new(create_test_context()).unwrap();
|
let client = SummonClient::new(create_test_context()).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user