Make research skill self-contained without external scripts
- Rewrite SKILL.md with inline instructions to spawn g3 --agent scout directly - Extend read_file to handle embedded skill paths (<embedded:name>/SKILL.md) - Remove scripts field from EmbeddedSkill struct (no longer needed) - Delete extraction.rs module (was only for script extraction) - Delete g3-research bash script - Remove obsolete Async Research Tool section from workspace memory Skills are now fully portable - they work when g3 is installed as a binary without access to source files. Agents can read embedded skill content via read_file with the special <embedded:...> path syntax.
This commit is contained in:
@@ -9,6 +9,7 @@ use tracing::debug;
|
||||
use crate::ui_writer::UiWriter;
|
||||
use crate::utils::resolve_path_with_unicode_fallback;
|
||||
use crate::utils::apply_unified_diff_to_string;
|
||||
use crate::skills::get_embedded_skill;
|
||||
use crate::ToolCall;
|
||||
|
||||
use super::executor::ToolContext;
|
||||
@@ -68,6 +69,28 @@ fn calculate_read_limit(file_bytes: usize, total_tokens: u32, used_tokens: u32)
|
||||
Some(max_bytes)
|
||||
}
|
||||
|
||||
/// Try to read an embedded skill by path.
|
||||
///
|
||||
/// Recognizes paths like:
|
||||
/// - `<embedded:research>/SKILL.md`
|
||||
/// - `<embedded:skill-name>/SKILL.md`
|
||||
///
|
||||
/// Returns the skill content if found, None otherwise.
|
||||
fn try_read_embedded_skill(path: &str) -> Option<&'static str> {
|
||||
// Check for embedded skill path pattern: <embedded:name>/SKILL.md
|
||||
if !path.starts_with("<embedded:") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Extract skill name from path like "<embedded:research>/SKILL.md"
|
||||
let after_prefix = path.strip_prefix("<embedded:")?;
|
||||
let skill_name = after_prefix.split('>').next()?;
|
||||
|
||||
// Look up the embedded skill
|
||||
let skill = get_embedded_skill(skill_name)?;
|
||||
Some(skill.skill_md)
|
||||
}
|
||||
|
||||
/// Execute the `read_file` tool.
|
||||
pub async fn execute_read_file<W: UiWriter>(
|
||||
tool_call: &ToolCall,
|
||||
@@ -80,13 +103,7 @@ pub async fn execute_read_file<W: UiWriter>(
|
||||
None => return Ok("❌ Missing file_path argument".to_string()),
|
||||
};
|
||||
|
||||
// Expand tilde (~) to home directory
|
||||
let expanded_path = shellexpand::tilde(file_path);
|
||||
// Try to resolve with Unicode space fallback (macOS uses U+202F in screenshot names)
|
||||
let resolved_path = resolve_path_with_unicode_fallback(expanded_path.as_ref());
|
||||
let path_str = resolved_path.as_ref();
|
||||
|
||||
// Extract optional start and end positions
|
||||
// Extract optional start and end positions (needed for both embedded and file reads)
|
||||
let start_char = tool_call
|
||||
.args
|
||||
.get("start")
|
||||
@@ -98,6 +115,25 @@ pub async fn execute_read_file<W: UiWriter>(
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|n| n as usize);
|
||||
|
||||
// Check for embedded skill paths (e.g., "<embedded:research>/SKILL.md")
|
||||
if let Some(content) = try_read_embedded_skill(file_path) {
|
||||
let total_len = content.len();
|
||||
let start = start_char.unwrap_or(0);
|
||||
let end = end_char.unwrap_or(total_len).min(total_len);
|
||||
if start >= total_len {
|
||||
return Ok(format!("❌ Start position {} exceeds embedded skill length {}", start, total_len));
|
||||
}
|
||||
let slice = &content[start..end];
|
||||
let line_count = slice.lines().count();
|
||||
return Ok(format!("{}\n🔍 {} lines read (embedded skill)", slice, line_count));
|
||||
}
|
||||
|
||||
// Expand tilde (~) to home directory
|
||||
let expanded_path = shellexpand::tilde(file_path);
|
||||
// Try to resolve with Unicode space fallback (macOS uses U+202F in screenshot names)
|
||||
let resolved_path = resolve_path_with_unicode_fallback(expanded_path.as_ref());
|
||||
let path_str = resolved_path.as_ref();
|
||||
|
||||
debug!(
|
||||
"Reading file: {}, start={:?}, end={:?}",
|
||||
path_str, start_char, end_char
|
||||
|
||||
Reference in New Issue
Block a user