Skip to content

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

Open
lifeizhou-ap wants to merge 18 commits into
mainfrom
lifei/acp-slash-commands
Open

feat: slash commands (built-in, skill, recipe) in acp server#9238
lifeizhou-ap wants to merge 18 commits into
mainfrom
lifei/acp-slash-commands

Conversation

@lifeizhou-ap
Copy link
Copy Markdown
Collaborator

@lifeizhou-ap lifeizhou-ap commented May 15, 2026

Summary

Wires builtin, skill, and recipe slash commands into the ACP available_commands update so ACP clients get a unified list, and restructures the slash-command code into a proper module with first-class skill and recipe handlers (incl. argument parsing).

Changes

ACP: surface all slash commands

  • ACP available_commands_update now returns builtins + recipes + skills in one list, with name-collision precedence builtin > recipe > skill.
  • UI: MentionPopover now inserts /skill-name for skill items, matching builtin/recipe behavior (previously inserted prose "Use the X skill to ").

New slash_commands/ module

Replaces the old single-file crates/goose/src/slash_commands.rs with a module that splits recipe, skill, into their own files and merges them into a unified list for ACP.

Skill slash commands

  • Skills now resolvable as /skill-name [args].
  • Skills can declare argument placeholders in their content and have them filled in at invocation time: a raw $ARGUMENTS blob, positional tokens ($1, $ARGUMENTS[0]), or named args ($component, $from) tied to the skill's declared argument names. Skills with no placeholders still receive the raw args appended so the model can see them.
  • Argument metadata location: currently read from metadata.argument-hint and metadata.arguments (per the agentskills.io frontmatter spec). Open question whether to promote these to top-level frontmatter to match Claude Code — happy to discuss.
  • argument-hint from frontmatter is surfaced as the ACP input_hint.
  • Refactored: skill context rendering is now shared between the load_skill MCP tool and the slash-command path. The load_skill tool also accepts an optional args field.

Recipe slash commands

  • Multi-parameter support: required params are positional, optional params are --flag value. Example: /migrate "Button Group" old-lib --to new-lib.
  • Single-required-param recipes still accept the full args string greedily (so /weather Melbourne weather works without quoting).
  • Input hint rendered as <required1> <required2> [--optional <optional>] and shown in the ACP command list.
  • Removed the old "only 1 param supported" error path and the now-unused build_recipe_from_template_with_positional_params helper.
  • Storage: recipe→command mappings still live in the global config under slash_commands. Open question whether to move these to a per-project / discoverable location like skills — happy to discuss.

Misc cleanup

  • Moved split_quoted from goose-cli to shared goose::utils::split_command_args; reused by stdio extension parsing, recipe args, and skill args. Now also tolerates apostrophes inside unquoted words (O'Reilly, don't).
  • handle_skills_command (/skills listing) now delegates to skill_slash_command::format_installed_skills.

Testing

Unit Test
Manual testing in Desktop App
Created a simple client locally to test integrate with acp server

Open questions

  1. Should skill argument-hint / arguments live at the top level of frontmatter (Claude Code style) instead of under metadata:?
  2. Should recipe→slash-command mappings move out of the global config (e.g. project-discoverable recipes)?
  3. Do we need a custom api to get the available slash commands? Currently we have notification in new_session, load_session, fork_session with the available slash command. However, when user add a skills, recipe slash command, or change working directory, there is no other ways for client to get the latest slash commands. We can add notification in those individual triggers, but it is hard to manage. Instead, having a custom api to return those commands seems be easier for the client.

Next

  • migrate ui/desktop to use these acp protocols
  • agent slash command
  • plugin skills slash command such as `plugin_name: skill_name"
  • Handle SystemNotification Messages in acp as some of the slash command has/should generate these kind of messages

* main: (66 commits)
  Switch GH pages deploy to actions/artifact workflow (#9025)
  fix(summon): re-apply canonical limits when delegate overrides model (#9183)
  Split code signing from build (#8587)
  refactor(logging): consolidate logging setup into shared helper in goose crate (#8817)
  fix(cli): report cumulative total_tokens in stream-json/json output (#8910)
  plugins: add open plugins (just skills for now) (#9063)
  fix(providers): refresh GCP metadata server token on expiration (#8929)
  chore(deps): bump the cargo-minor-and-patch group across 1 directory with 14 updates (#9178)
  chore(deps): bump bzip2 from 0.5.2 to 0.6.1 (#8964)
  chore(deps): bump tauri from 2.10.3 to 2.11.1 in /ui/goose2/src-tauri (#9066)
  chore(deps): bump hono from 4.12.14 to 4.12.18 in /evals/open-model-gym/mcp-harness (#9073)
  localize hardcoded strings in provider settings UI (#8931)
  chore(deps): bump @babel/plugin-transform-modules-systemjs from 7.28.5 to 7.29.4 in /documentation (#9122)
  move settings into app shell (#9047)
  Add Location column to CLI skills table (#8785)
  (feat): add routstr as a declarative provider (#9175)
  Add FuturMix provider (#8840)
  fix: convert quoted numeric config values to numbers if needed (#8844)
  fix(ui): keep SSE reconnect loop alive on long disconnects (#8717) (#8846)
  fix(openai): apply request_params to outgoing API payload (#9151)
  ...
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 218e9c8e8c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/utils.rs Outdated
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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop treating apostrophes as quote delimiters

split_command_args flips in_single_quote on every ', so ordinary text like O'Reilly is parsed as an unmatched quote and returned as an error. This parser is now used by recipe_slash_command::parse_recipe_args and skills::apply_skill_arguments, so slash-command recipes/skills that accept free-text arguments can fail on common apostrophes even when users are not trying to quote. Please only start single-quote mode when ' begins a quoted token (or otherwise support apostrophes inside unquoted words).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3afe8678fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +233 to +236
let known_keys: HashSet<&str> = required
.iter()
.chain(optional.iter())
.map(|p| p.key.as_str())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject required params passed as flags

parse_recipe_args currently treats both required and optional keys as valid --flag names, but the required-position tracking (required_idx) is only advanced by positional tokens. In a mixed call like --component Button old-lib, the positional token is incorrectly assigned back to component, which overwrites the flagged value and leaves from missing. This produces confusing "missing parameter" failures for otherwise reasonable input; either disallow --<required> entirely or mark required params as consumed when seen as flags.

Useful? React with 👍 / 👎.

Comment on lines +26 to +30
for command in super::recipe_slash_command::commands_from_mappings(
super::recipe_slash_command::list_commands(),
) {
if reserved_names.insert(command.name.clone()) {
commands.push(command);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reserve names for all mapped recipe commands

The ACP command list only reserves names from recipes that successfully validate in commands_from_mappings, then it exposes same-named skills. Execution still checks raw recipe mappings first and returns a recipe error on invalid files, so users can be shown a skill command that is not actually invokable when an invalid mapped recipe with the same name exists. Reserve names from all configured recipe mappings (or fall back to skill resolution on recipe-validation errors) to keep discovery and execution consistent.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1cfb00b5e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +252 to +253
if value.starts_with("--") {
return Err(anyhow!("Missing value for --{}", flag));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept '--' prefixed strings as flagged recipe values

The flag parser currently rejects any flag value whose token starts with --, so valid quoted inputs like /deploy service --args "--dry-run --force" are treated as Missing value for --args even though a value was provided. This blocks a common recipe pattern where a parameter is meant to carry downstream CLI options, because split_command_args strips quotes before this check and the value still begins with --.

Useful? React with 👍 / 👎.

fn builtin_input_hint(command: &str) -> Option<&'static str> {
match command {
"prompt" => Some("<name> [--info] [key=value...]"),
"prompts" => Some("[--extension <name>]"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align /prompts input hint with actual argument parsing

The ACP command metadata advertises /prompts [--extension <name>], but command execution does not parse a --extension flag (it treats the first token as the raw extension name in handle_prompts_command). Following the published hint therefore fails with an "Extension '--extension' not found" style error instead of filtering prompts, so discovery metadata and runtime behavior diverge.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f30d691a5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

self.send_available_commands_update(cx, &args.session_id)
.await?;

Self::send_available_commands_update(cx, &args.session_id, &args.cwd)?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use persisted cwd when publishing load_session commands

on_load_session builds the available-command list from args.cwd, but slash-skill execution later resolves skills from the session’s stored working directory (handle_skill_command reads it via session_manager.get_session). When those differ (for example clients that call load with a generic cwd like "~"), ACP advertises skills that are not actually runnable in that loaded session, causing /skill commands suggested by discovery to fail at runtime. Build the command update from goose_session.working_dir to keep discovery and execution aligned.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think args.cwd is intentional here. on_load_session updates the persisted session working_dir before publishing commands:

  self.session_manager
      .update(&session_id)
      .working_dir(args.cwd.clone())
      .apply()
      .await?;

Slash-skill execution later reloads the session from session_manager, so it should resolve skills from that same updated cwd.

@lifeizhou-ap lifeizhou-ap changed the title Lifei/acp slash commands feat: slash commands (built-in, skill, recipe) in acp server May 15, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dbcad75a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/utils.rs
for c in input.chars() {
match c {
'"' if !in_single_quote => in_double_quote = !in_double_quote,
'\'' if !in_double_quote && (in_single_quote || current.is_empty()) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor single-quoted substrings inside tokens

The new single-quote guard only enters quote mode when current.is_empty(), so inputs like ENV='two words' cmd or --note='needs review' are tokenized at the internal space instead of being kept as one argument. This is a regression from the previous parser behavior and now affects every caller of split_command_args (notably stdio extension parsing plus recipe/skill slash-argument parsing), causing valid quoted values attached to env/flag prefixes to be misparsed or rejected.

Useful? React with 👍 / 👎.

@DOsinga DOsinga added the needs_human label to set when a robot looks at a PR and can't handle it label May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs_human label to set when a robot looks at a PR and can't handle it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants