feat: slash commands (built-in, skill, recipe) in acp server (#9238)

This commit is contained in:
Lifei Zhou
2026-05-20 14:48:34 +10:00
committed by GitHub
parent bc6293ce70
commit 3631e3cbf1
20 changed files with 1465 additions and 433 deletions
+1 -1
View File
@@ -1117,7 +1117,7 @@ fn configure_stdio_extension() -> anyhow::Result<()> {
let timeout = prompt_extension_timeout()?;
let mut parts = crate::session::split_quoted(&command_str)?;
let mut parts = goose::utils::split_command_args(&command_str)?;
let cmd = if parts.is_empty() {
String::new()
} else {
+6 -36
View File
@@ -239,36 +239,6 @@ pub async fn classify_planner_response(
}
}
pub fn split_quoted(input: &str) -> Result<Vec<String>> {
let mut parts = Vec::new();
let mut current = String::new();
let mut in_double_quote = false;
let mut in_single_quote = false;
for c in input.chars() {
match c {
'"' if !in_single_quote => in_double_quote = !in_double_quote,
'\'' if !in_double_quote => in_single_quote = !in_single_quote,
c if c.is_whitespace() && !in_double_quote && !in_single_quote => {
if !current.is_empty() {
parts.push(std::mem::take(&mut current));
}
}
_ => current.push(c),
}
}
if in_double_quote || in_single_quote {
return Err(anyhow::anyhow!("Unmatched quote in command"));
}
if !current.is_empty() {
parts.push(current);
}
Ok(parts)
}
impl CliSession {
#[allow(clippy::too_many_arguments)]
pub async fn new(
@@ -311,7 +281,7 @@ impl CliSession {
/// Parse a stdio extension command string into an ExtensionConfig
/// Format: "ENV1=val1 ENV2=val2 command args..."
pub fn parse_stdio_extension(extension_command: &str) -> Result<ExtensionConfig> {
let mut parts = split_quoted(extension_command)?;
let mut parts = goose::utils::split_command_args(extension_command)?;
let mut envs = HashMap::new();
while let Some(part) = parts.first() {
@@ -2250,20 +2220,20 @@ mod tests {
}
#[test]
fn test_split_quoted_windows_paths() {
fn test_split_command_args_windows_paths() {
assert_eq!(
split_quoted(r"C:\tools\mcp.exe --arg value").unwrap(),
goose::utils::split_command_args(r"C:\tools\mcp.exe --arg value").unwrap(),
vec![r"C:\tools\mcp.exe", "--arg", "value"]
);
assert_eq!(
split_quoted(r#""C:\Program Files\server\mcp.exe" --arg"#).unwrap(),
goose::utils::split_command_args(r#""C:\Program Files\server\mcp.exe" --arg"#).unwrap(),
vec![r"C:\Program Files\server\mcp.exe", "--arg"]
);
}
#[test]
fn test_split_quoted_unmatched_quote() {
assert!(split_quoted(r#""unmatched"#).is_err());
fn test_split_command_args_unmatched_quote() {
assert!(goose::utils::split_command_args(r#""unmatched"#).is_err());
}
#[test_case(