diff --git a/thoughts/shared/plans/2025-08-28_20-04-42_agent-panel-slash-command-menu.md b/thoughts/shared/plans/2025-08-28_20-04-42_agent-panel-slash-command-menu.md index 6c0274e9d93af20b150f524a66425acf2bfb10c7..54ded1897c5715b63975fa2588a4149ad93b7ca1 100644 --- a/thoughts/shared/plans/2025-08-28_20-04-42_agent-panel-slash-command-menu.md +++ b/thoughts/shared/plans/2025-08-28_20-04-42_agent-panel-slash-command-menu.md @@ -45,6 +45,66 @@ When user types "/" in agent panel message editor: Follow the existing "@" mention completion pattern but trigger on "/" instead. Use capability negotiation to control menu visibility. Extend ACP integration to call new RPC methods when available. +## Repository Dependencies & PR Strategy + +### **Multi-Repository Architecture** +This feature spans three repositories that must be coordinated: + +1. **`agent-client-protocol`**: External crate defining the protocol +2. **`zed`**: Main editor with ACP client integration +3. **`claude-code-acp`**: Reference agent implementation + +### **Dependency Chain** +``` +agent-client-protocol (Phase 1) + ↓ +zed (Phase 2) - temporarily depends on local ACP changes + ↓ +claude-code-acp (Phase 3) - uses published ACP version +``` + +### **Development Workflow** + +#### Step 1: Local Development Setup +```bash +# Work on ACP protocol extension locally +cd /Users/nathan/src/agent-client-protocol +# Make Phase 1 changes... + +# Point Zed to local ACP version for testing +cd /Users/nathan/src/zed +# Update Cargo.toml to reference local path: +# agent-client-protocol = { path = "../agent-client-protocol" } +``` + +#### Step 2: Testing & Validation +- Test all phases end-to-end with local dependencies +- Verify Phase 1+2 integration works correctly +- Validate Phase 3 against local ACP changes + +#### Step 3: PR Sequence +1. **First PR**: `agent-client-protocol` with new slash command methods +2. **Second PR**: `zed` referencing published ACP version (after #1 merges) +3. **Third PR**: `claude-code-acp` using new ACP capabilities + +### **Temporary Dependency Management** +During development, Zed's `Cargo.toml` will need: +```toml +[dependencies] +# Temporary local reference for development/testing +agent-client-protocol = { path = "../agent-client-protocol" } + +# After ACP PR merges, switch to: +agent-client-protocol = "0.2.0-alpha.1" # or appropriate version +``` + +### **Cross-Repository Verification** +Before opening PRs: +- [ ] ACP protocol extension compiles and tests pass +- [ ] Zed compiles against local ACP changes +- [ ] End-to-end slash command flow works locally +- [ ] Claude ACP adapter works with generated types + ## Phase 1: ACP Protocol Extension ### Overview @@ -54,29 +114,18 @@ Add new slash command RPC methods and capabilities to the agent-client-protocol #### 1. Protocol Types (External Crate) **File**: `/Users/nathan/src/agent-client-protocol/rust/agent.rs` -**Changes**: Add new request/response types and trait methods +**Changes**: Add new request/response types and trait methods following exact ACP patterns +**Step 1: Add Method Constants** (after line 415): ```rust -// Add around line 108 after the cancel method in the Agent trait -/// Lists available custom commands for a session. -/// -/// Returns all commands available in the agent's `.claude/commands` directory -/// or equivalent command registry. Commands can be executed via `run_command`. -fn list_commands( - &self, - arguments: ListCommandsRequest, -) -> impl Future>; - -/// Executes a custom command within a session. -/// -/// Runs the specified command with optional arguments. The agent should -/// stream results back via session update notifications. -fn run_command( - &self, - arguments: RunCommandRequest, -) -> impl Future>; +/// Method name for listing custom commands in a session. +pub const SESSION_LIST_COMMANDS: &str = "session/list_commands"; +/// Method name for running a custom command in a session. +pub const SESSION_RUN_COMMAND: &str = "session/run_command"; +``` -// Add around line 372 after PromptCapabilities +**Step 2: Add Request/Response Structs** (after PromptCapabilities at line 371): +```rust /// Request parameters for listing available commands. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[schemars(extend("x-side" = "agent", "x-method" = "session/list_commands"))] @@ -121,6 +170,47 @@ pub struct RunCommandRequest { } ``` +**Step 3: Add Agent Trait Methods** (after `cancel()` method at line 107): +```rust +/// Lists available custom commands for a session. +/// +/// Returns all commands available in the agent's `.claude/commands` directory +/// or equivalent command registry. Commands can be executed via `run_command`. +fn list_commands( + &self, + arguments: ListCommandsRequest, +) -> impl Future>; + +/// Executes a custom command within a session. +/// +/// Runs the specified command with optional arguments. The agent should +/// stream results back via session update notifications. +fn run_command( + &self, + arguments: RunCommandRequest, +) -> impl Future>; +``` + +**Step 4: Add Enum Routing Variants** (to `ClientRequest` enum around line 423): +```rust +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ClientRequest { + InitializeRequest(InitializeRequest), + AuthenticateRequest(AuthenticateRequest), + NewSessionRequest(NewSessionRequest), + LoadSessionRequest(LoadSessionRequest), + PromptRequest(PromptRequest), + ListCommandsRequest(ListCommandsRequest), // ADD THIS + RunCommandRequest(RunCommandRequest), // ADD THIS +} +``` + +**Step 5: Add AgentResponse Enum Variant** (find AgentResponse enum): +```rust +ListCommandsResponse(ListCommandsResponse), // ADD THIS +``` + #### 2. Capability Extension **File**: `/Users/nathan/src/agent-client-protocol/rust/agent.rs` **Changes**: Extend PromptCapabilities with custom command support @@ -157,10 +247,40 @@ fn run_command(&self, request: acp::RunCommandRequest, cx: &mut App) -> Task Result { + self.connection + .request(acp::ClientRequest::ListCommandsRequest(request)) + .await + .and_then(|response| match response { + acp::AgentResponse::ListCommandsResponse(response) => Ok(response), + _ => Err(acp::Error::internal_error("Invalid response type")), + }) + } + + /// Executes a custom command in a session. + pub async fn run_command( + &self, + request: acp::RunCommandRequest, + ) -> Result<(), acp::Error> { + self.connection + .request(acp::ClientRequest::RunCommandRequest(request)) + .await + .map(|_| ()) + } +} +``` + +**Step 2: Implement AgentConnection Trait Methods** (for AcpConnection struct): ```rust -// Add around line 340 after existing method implementations fn list_commands(&self, session_id: &acp::SessionId, cx: &mut App) -> Task> { let conn = self.connection.clone(); let session_id = session_id.clone(); @@ -177,6 +297,25 @@ fn run_command(&self, request: acp::RunCommandRequest, cx: &mut App) -> Task { + if let Ok(request) = serde_json::from_value::(params) { + Ok(acp::ClientRequest::ListCommandsRequest(request)) + } else { + Err(acp::Error::invalid_params("Invalid list_commands parameters")) + } +} +acp::SESSION_RUN_COMMAND => { + if let Ok(request) = serde_json::from_value::(params) { + Ok(acp::ClientRequest::RunCommandRequest(request)) + } else { + Err(acp::Error::invalid_params("Invalid run_command parameters")) + } +} +``` + #### 5. Capability Detection **File**: `crates/acp_thread/src/acp_thread.rs` **Changes**: Add capability checking helper @@ -194,14 +333,23 @@ pub fn supports_custom_commands(&self, cx: &App) -> bool { - [ ] Protocol crate compiles: `cd /Users/nathan/src/agent-client-protocol && cargo check` - [ ] Protocol tests pass: `cd /Users/nathan/src/agent-client-protocol && cargo test` - [ ] Schema generation works: `cd /Users/nathan/src/agent-client-protocol && cargo run --bin generate` +- [ ] Schema includes new methods: `grep -A5 -B5 "session/list_commands\|session/run_command" /Users/nathan/src/agent-client-protocol/schema/schema.json` - [ ] Zed compiles successfully: `./script/clippy` - [ ] No linting errors: `cargo clippy --package agent_servers --package acp_thread` +- [ ] ACP thread capability method compiles: `cargo check --package acp_thread` #### Manual Verification: -- [ ] New trait methods are properly defined in connection interface -- [ ] ACP connection implements new methods correctly -- [ ] Capability detection helper is available for UI layer -- [ ] JSON schema includes new request/response types +- [ ] New trait methods are properly defined in Agent trait (`/Users/nathan/src/agent-client-protocol/rust/agent.rs`) + - Verify `list_commands()` method signature at line ~115 + - Verify `run_command()` method signature at line ~127 +- [ ] Request/response enums updated in ClientRequest (`agent.rs:~423`) and AgentResponse enums +- [ ] Method constants added (`SESSION_LIST_COMMANDS`, `SESSION_RUN_COMMAND`) after line 415 +- [ ] PromptCapabilities extended with `supports_custom_commands: bool` field +- [ ] ClientSideConnection methods implemented with proper error handling +- [ ] Message dispatch logic updated in `AgentSide::decode_request()` +- [ ] AgentConnection trait extends with new methods (`crates/acp_thread/src/connection.rs`) +- [ ] AcpConnection implements trait methods (`crates/agent_servers/src/acp.rs`) +- [ ] AcpThread has `supports_custom_commands()` helper method --- @@ -259,43 +407,65 @@ impl SlashCommandCompletion { #### 2. Completion Trigger Detection **File**: `crates/agent_ui/src/acp/completion_provider.rs` -**Changes**: Extend `is_completion_trigger()` method +**Changes**: Extend `is_completion_trigger()` method following existing patterns + +**Current Pattern Analysis**: The completion provider implements the `CompletionProvider` trait and integrates with the editor's completion system. The `is_completion_trigger()` method at line 763 currently only handles "@" mentions. ```rust -// Modify around line 763 to add slash command detection -pub fn is_completion_trigger( - &self, - buffer: &Entity, - position: language::Anchor, - text: &str, - _trigger_in_words: bool, - cx: &mut Context, -) -> bool { - // Existing @ mention logic... - if let Some(_) = MentionCompletion::try_parse(&line, position.column) { - return true; - } - - // Add slash command detection - if let Some(thread) = &self.thread { - if thread.read(cx).supports_custom_commands(cx) { - if let Some(_) = SlashCommandCompletion::try_parse(&line, position.column) { - return true; +// Modify the existing is_completion_trigger() method around line 763 +impl CompletionProvider for ContextPickerCompletionProvider { + fn is_completion_trigger( + &self, + buffer: &Entity, + position: language::Anchor, + text: &str, + _trigger_in_words: bool, + cx: &mut Context, + ) -> bool { + let buffer = buffer.read(cx); + let position = position.to_point(&buffer); + let line_start = Point::new(position.row, 0); + let mut lines = buffer.text_for_range(line_start..position).lines(); + let Some(line) = lines.next() else { + return false; + }; + + // Existing @ mention logic - KEEP THIS + if let Some(_) = MentionCompletion::try_parse(&line, position.column) { + return true; + } + + // ADD: Slash command detection (only if agent supports commands) + if let Some(thread) = &self.thread { + if thread.read(cx).supports_custom_commands(cx) { + if let Some(_) = SlashCommandCompletion::try_parse(&line, position.column) { + return true; + } } } + + false } - - false } ``` +**Pattern Notes**: +- Integrates with existing `@` mention system without conflicts +- Only triggers when agent capability `supports_custom_commands` is true +- Uses same line parsing approach as existing mention system +- Maintains backward compatibility + #### 3. Command Completion Generation **File**: `crates/agent_ui/src/acp/completion_provider.rs` -**Changes**: Extend `completions()` method +**Changes**: Extend `completions()` method using existing async patterns + +**Current Pattern Analysis**: The completion provider's `completions()` method at line 639 returns `Task>>` and uses `cx.spawn()` for async operations. It handles different completion types via pattern matching. ```rust -// Add around line 700 in completions() method after mention handling -// Handle slash command completions +// Modify the existing completions() method around line 700 +// ADD this after the existing mention completion logic: + +// Handle slash command completions (only if agent supports them) if let Some(thread) = &self.thread { if thread.read(cx).supports_custom_commands(cx) { if let Some(slash_completion) = SlashCommandCompletion::try_parse(&line, cursor_offset) { @@ -309,7 +479,7 @@ if let Some(thread) = &self.thread { } } -// Add new method around line 850 +// ADD new method following existing async patterns (around line 850): fn complete_slash_commands( &self, completion: SlashCommandCompletion, @@ -322,23 +492,28 @@ fn complete_slash_commands( }; cx.spawn(async move |cx| { + // Get session info using existing patterns let session_id = thread.read_with(cx, |thread, _| thread.session_id().clone())?; let connection = thread.read_with(cx, |thread, _| thread.connection().clone())?; - // Fetch commands from agent - let commands = connection.list_commands(&session_id, cx).await?; + // Fetch commands from agent via new ACP method + let response = connection.list_commands(&session_id, cx).await?; - // Filter commands matching typed prefix - let matching_commands: Vec<_> = commands + // Filter commands matching typed prefix (fuzzy matching like mentions) + let matching_commands: Vec<_> = response.commands .into_iter() - .filter(|cmd| cmd.name.starts_with(&completion.name)) + .filter(|cmd| { + // Support both prefix matching and fuzzy matching + cmd.name.starts_with(&completion.name) || + cmd.name.to_lowercase().contains(&completion.name.to_lowercase()) + }) .collect(); - // Convert to completion responses + // Convert to project::Completion following existing patterns let mut completions = Vec::new(); for command in matching_commands { let new_text = format!("/{}", command.name); - let completion = project::Completion { + let completion_item = project::Completion { old_range: completion.source_range.clone(), new_text, label: command.name.clone().into(), @@ -349,6 +524,7 @@ fn complete_slash_commands( } else { None }, + // Custom confirmation handler for command execution confirm: Some(Arc::new(SlashCommandConfirmation { command: command.name, requires_argument: command.requires_argument, @@ -356,9 +532,10 @@ fn complete_slash_commands( })), ..Default::default() }; - completions.push(completion); + completions.push(completion_item); } + // Return single completion response (like existing mentions) Ok(vec![project::CompletionResponse { completions, is_incomplete: false, @@ -367,6 +544,13 @@ fn complete_slash_commands( } ``` +**Integration Notes**: +- Follows same async pattern as existing mention completions at line 639 +- Uses `thread.read_with()` pattern for safe entity access +- Implements fuzzy matching similar to existing completion types +- Returns single `CompletionResponse` following established patterns +- Integrates custom confirmation handler via `confirm` field + #### 4. Command Confirmation Handler **File**: `crates/agent_ui/src/acp/completion_provider.rs` **Changes**: Add confirmation handler for slash commands @@ -453,13 +637,30 @@ pub fn run_command( - [ ] Code compiles successfully: `./script/clippy` - [ ] No linting errors: `cargo clippy --package agent_ui --package acp_thread` - [ ] Type checking passes: `cargo check --package agent_ui --package acp_thread` +- [ ] Completion provider compiles: `cargo check --package agent_ui --lib` +- [ ] Slash command parsing works: Test `SlashCommandCompletion::try_parse()` with various inputs #### Manual Verification: -- [ ] Typing "/" in agent panel triggers completion when agent supports commands -- [ ] No "/" completion appears when agent doesn't support commands -- [ ] Command list fetched from agent via `list_commands()` RPC -- [ ] Command selection triggers `run_command()` RPC -- [ ] Menu dismisses properly on Escape or click-outside +- [ ] SlashCommandCompletion struct added to `crates/agent_ui/src/acp/completion_provider.rs` + - Verify `try_parse()` method implementation + - Test parsing of "/", "/create", "/research_codebase" patterns +- [ ] ContextPickerCompletionProvider updated: + - `is_completion_trigger()` method extended (around line 763) + - `completions()` method handles slash commands (around line 700) + - `complete_slash_commands()` method added (around line 850) +- [ ] SlashCommandConfirmation struct implements CompletionConfirm trait + - Handles immediate execution for commands without arguments + - Allows continued typing for commands requiring arguments +- [ ] AcpThread has `run_command()` method for command execution +- [ ] Integration Testing: + - [ ] Typing "/" in agent panel triggers completion when `supports_custom_commands = true` + - [ ] No "/" completion appears when `supports_custom_commands = false` + - [ ] Command list fetched from agent via `list_commands()` RPC call + - [ ] Command selection triggers `run_command()` RPC call + - [ ] Menu shows command descriptions from agent + - [ ] Fuzzy matching works (typing "/cr" shows "create_plan") + - [ ] Menu dismisses properly on Escape or click-outside + - [ ] Commands execute and stream results back to thread view --- @@ -468,11 +669,19 @@ pub fn run_command( ### Overview Prepare the Claude Code ACP adapter to implement the new slash command RPC methods by adding command parsing and execution. +### **CRITICAL ARCHITECTURE NOTE** +The TypeScript types in `claude-code-acp` are **automatically generated** from the Rust protocol definitions. The ACP repository uses a code generation pipeline: + +1. **Rust → JSON Schema**: `cargo run --bin generate` creates `schema/schema.json` from Rust types +2. **JSON Schema → TypeScript**: `node typescript/generate.js` creates TypeScript types from the schema + +**This means the new `ListCommandsRequest`, `RunCommandRequest`, etc. types will be automatically available in TypeScript after we extend the Rust protocol in Phase 1.** + ### Changes Required: #### 1. Command Parsing Module **File**: `claude-code-acp/src/command-parser.ts` (new file) -**Changes**: Add markdown command parser +**Changes**: Add markdown command parser (TypeScript types will be auto-generated from Phase 1) ```typescript import * as fs from 'fs'; @@ -579,133 +788,199 @@ export class CommandParser { } ``` -#### 2. ACP Agent Method Implementation -**File**: `claude-code-acp/src/acp-agent.ts` -**Changes**: Add new RPC method handlers +#### 2. Regenerate TypeScript Types +**Prerequisites**: After completing Phase 1 Rust protocol extension +**Commands**: Generate TypeScript types from updated Rust definitions -```typescript -// Add import -import { CommandParser, CommandInfo } from './command-parser'; +```bash +# From agent-client-protocol repository root: +cd /Users/nathan/src/agent-client-protocol +npm run generate +``` -// Add to ClaudeAcpAgent class around line 50 -private commandParser?: CommandParser; +This will automatically create TypeScript types for: +- `ListCommandsRequest` +- `ListCommandsResponse` +- `RunCommandRequest` +- `CommandInfo` +- Updated `PromptCapabilities` with `supports_custom_commands` -// Modify constructor around line 100 -constructor(options: ClaudeAcpAgentOptions) { - // ... existing initialization - - // Initialize command parser if .claude/commands exists - if (options.cwd) { - this.commandParser = new CommandParser(options.cwd); - } -} +#### 3. ACP Agent Method Implementation +**File**: `claude-code-acp/src/acp-agent.ts` +**Changes**: Add new RPC method handlers following existing session management patterns -// Add capability declaration in initialize() around line 150 -async initialize(request: InitializeRequest): Promise { - return { - protocol_version: VERSION, - agent_capabilities: { - prompt_capabilities: { - image: true, - audio: false, - embedded_context: true, - supports_custom_commands: !!this.commandParser, // Enable if commands exist - }, - }, - auth_methods: ['claude-code'], - }; -} +**Current Architecture Analysis**: The `ClaudeAcpAgent` at line 51 implements the ACP `Agent` interface with UUID-based session management. Sessions use Claude SDK `Query` objects with MCP proxy integration. The `prompt()` method at line 140 shows the pattern for query execution and result streaming. -// Add new RPC method handlers around line 400 -async listCommands(request: ListCommandsRequest): Promise { - if (!this.commandParser) { - return { commands: [] }; +```typescript +// Step 1: Add import for command parser and auto-generated ACP types +import { CommandParser, CommandInfo } from './command-parser'; +import type { + ListCommandsRequest, + ListCommandsResponse, + RunCommandRequest +} from '@zed-industries/agent-client-protocol'; + +// Step 2: Extend ClaudeAcpAgent class (add to class definition around line 51) +export class ClaudeAcpAgent implements Agent { + private sessions: Map = new Map(); + private client: Client; + private commandParser?: CommandParser; // ADD THIS + + // Step 3: Modify constructor to initialize command parser (around line 60) + constructor( + client: Client, + options: { cwd?: string } = {} + ) { + this.client = client; + + // Initialize command parser if .claude/commands directory exists + if (options.cwd && fs.existsSync(path.join(options.cwd, '.claude', 'commands'))) { + this.commandParser = new CommandParser(options.cwd); + } } - try { - const commands = await this.commandParser.listCommands(); + // Step 4: Update initialize() method to advertise capability (around line 68) + async initialize(request: InitializeRequest): Promise { return { - commands: commands.map(cmd => ({ - name: cmd.name, - description: cmd.description, - requires_argument: cmd.requires_argument, - })) + protocol_version: VERSION, + agent_capabilities: { + prompt_capabilities: { + image: true, + audio: false, + embedded_context: true, + supports_custom_commands: !!this.commandParser, // Advertise support + }, + }, + auth_methods: [/* existing auth methods */], }; - } catch (error) { - console.error('Failed to list commands:', error); - return { commands: [] }; } -} -async runCommand(request: RunCommandRequest): Promise { - if (!this.commandParser) { - throw new Error('Commands not supported'); - } - - const command = await this.commandParser.getCommand(request.command); - if (!command) { - throw new Error(`Command not found: ${request.command}`); - } + // Step 5: Implement listCommands following existing async patterns (after line 218) + async listCommands(request: ListCommandsRequest): Promise { + if (!this.commandParser) { + return { commands: [] }; + } - // Execute command by sending its content as a system prompt to Claude SDK - const session = this.sessions.get(request.session_id); - if (!session) { - throw new Error('Session not found'); + try { + const commands = await this.commandParser.listCommands(); + return { + commands: commands.map(cmd => ({ + name: cmd.name, + description: cmd.description, + requires_argument: cmd.requires_argument, + })) + }; + } catch (error) { + console.error('Failed to list commands:', error); + return { commands: [] }; + } } - try { - let systemPrompt = command.content; - - // If command requires arguments and args provided, append them - if (command.requires_argument && request.args) { - systemPrompt += `\n\nArguments: ${request.args}`; + // Step 6: Implement runCommand integrating with existing session flow + async runCommand(request: RunCommandRequest): Promise { + if (!this.commandParser) { + throw new Error('Commands not supported'); } - // Create new query with command content as system prompt - const query = query({ - prompt: systemPrompt, - options: { - cwd: session.cwd, - mcpServers: session.mcpServers, - allowedTools: session.allowedTools, - disallowedTools: session.disallowedTools, - }, - }); + const command = await this.commandParser.getCommand(request.command); + if (!command) { + throw new Error(`Command not found: ${request.command}`); + } - // Stream results back to session - for await (const chunk of query) { - // Convert query response to session update format - const update = this.convertQueryChunkToSessionUpdate(chunk); - await this.sendSessionUpdate(request.session_id, update); + const session = this.sessions.get(request.session_id); + if (!session) { + throw new Error('Session not found'); } - } catch (error) { - console.error('Command execution failed:', error); - // Send error as session update - await this.sendSessionUpdate(request.session_id, { - type: 'agent_message_chunk', - content: { - type: 'text', - text: `Error executing command: ${error.message}`, - }, - }); + try { + // Build prompt from command content following existing patterns + let commandPrompt = command.content; + + if (command.requires_argument && request.args) { + commandPrompt += `\n\nArguments: ${request.args}`; + } + + // Execute via existing session input stream (recommended approach) + // This integrates with existing prompt() flow and MCP proxy + session.input.push({ + role: 'user', + content: commandPrompt + }); + + // Results will be streamed back via existing query execution loop + // at line 150 in prompt() method, no additional streaming needed + + } catch (error) { + console.error('Command execution failed:', error); + // Send error via existing session update mechanism + await this.client.sessionUpdate({ + session_id: request.session_id, + type: 'agent_message_chunk', + content: { + type: 'text', + text: `Error executing command: ${error.message}`, + }, + }); + } } } ``` +**Integration Notes**: +- **Auto-Generated Types**: All ACP protocol types are automatically generated from Rust definitions +- **Session Reuse**: Uses existing session's input stream and MCP configuration +- **Result Streaming**: Leverages existing `prompt()` method's streaming loop at line 150 +- **Error Handling**: Uses established session update patterns from line 191 +- **Tool Access**: Commands inherit session's MCP server and tool configurations + ### Success Criteria: #### Automated Verification: -- [ ] TypeScript compilation passes: `npm run typecheck` (in claude-code-acp) -- [ ] ESLint passes: `npm run lint` (in claude-code-acp) -- [ ] Command parser unit tests pass: `npm test command-parser` +- [ ] **Prerequisites completed**: Phase 1 Rust protocol extension must be completed first +- [ ] **TypeScript types generated**: `cd /Users/nathan/src/agent-client-protocol && npm run generate` +- [ ] **Types available**: Verify new types exist in `agent-client-protocol/typescript/schema.ts` +- [ ] TypeScript compilation passes: `cd /Users/nathan/src/claude-code-acp && npm run typecheck` +- [ ] ESLint passes: `cd /Users/nathan/src/claude-code-acp && npm run lint` +- [ ] Agent compiles: `cd /Users/nathan/src/claude-code-acp && npm run build` +- [ ] Command parser unit tests pass: `cd /Users/nathan/src/claude-code-acp && npm test -- --testNamePattern="command-parser"` #### Manual Verification: -- [ ] `.claude/commands/*.md` files are correctly parsed for metadata -- [ ] `list_commands()` returns available commands with descriptions -- [ ] `run_command()` executes command content via Claude SDK -- [ ] Command results stream back as session updates -- [ ] Error handling works for missing commands or execution failures +- [ ] **Code Generation Pipeline**: + - [ ] Rust protocol changes trigger successful schema generation: `cargo run --bin generate` + - [ ] JSON schema contains new method definitions: `grep -A5 -B5 "session/list_commands\|session/run_command" /Users/nathan/src/agent-client-protocol/schema/schema.json` + - [ ] TypeScript types generated correctly: Check for `ListCommandsRequest`, `RunCommandRequest` types in schema.ts +- [ ] CommandParser class implemented (`claude-code-acp/src/command-parser.ts`): + - `listCommands()` method reads `.claude/commands/*.md` files + - `parseCommandFile()` extracts H1 titles and descriptions correctly + - `getCommand()` returns full command content for execution + - Proper error handling for missing directories and files + - Command caching works correctly +- [ ] ClaudeAcpAgent class extended (`claude-code-acp/src/acp-agent.ts`): + - Constructor initializes `commandParser` when `.claude/commands` exists + - `initialize()` method advertises `supports_custom_commands` capability correctly + - `listCommands()` method implemented and returns properly formatted response + - `runCommand()` method integrated with existing session management + - Command execution uses existing session input stream + - Error handling streams errors back via session updates +- [ ] **Type Integration**: + - [ ] Auto-generated types imported correctly from `@zed-industries/agent-client-protocol` + - [ ] TypeScript compiler recognizes new protocol method signatures + - [ ] No type errors when implementing new agent methods +- [ ] Integration Testing: + - [ ] Agent advertises `supports_custom_commands = true` when `.claude/commands` directory exists + - [ ] Agent advertises `supports_custom_commands = false` when directory doesn't exist + - [ ] `list_commands()` RPC returns commands from `.claude/commands/*.md` files + - [ ] Commands include correct name, description, requires_argument fields + - [ ] `run_command()` executes command content via Claude SDK integration + - [ ] Command results stream back as session updates to ACP client + - [ ] Commands have access to session's MCP servers and tool permissions + - [ ] Error handling works for missing commands, directories, execution failures + - [ ] Command arguments are properly appended when provided +- [ ] End-to-End Testing: + - [ ] Create test `.claude/commands/test.md` file with sample command + - [ ] Verify command appears in Zed's "/" completion menu + - [ ] Verify command executes and streams results to agent panel + - [ ] Verify commands work with existing MCP proxy and tool permissions --- @@ -739,8 +1014,26 @@ async runCommand(request: RunCommandRequest): Promise { ## Migration Notes +### User Experience No migration needed - this is a new feature that gracefully degrades for agents that don't support custom commands. Existing agent panel behavior is preserved. +### Developer Coordination +**Important**: This feature requires coordinated releases across multiple repositories: + +1. **ACP Protocol**: Must be released first with new slash command methods +2. **Zed**: Can only merge after ACP release is available +3. **Agent Implementations**: Can adopt new capabilities independently + +### Version Compatibility +- **Backward Compatible**: Old agents continue working without slash command menus +- **Forward Compatible**: New Zed version works with old agents (feature simply disabled) +- **Graceful Degradation**: UI adapts based on agent-advertised capabilities + +### Rollout Strategy +1. **Phase 1 Release**: ACP protocol extension (no visible user changes) +2. **Phase 2 Release**: Zed UI implementation (menu appears only with compatible agents) +3. **Phase 3+ Rollout**: Agent implementations adopt new capabilities over time + ## References - Original research: `thoughts/shared/research/2025-08-28_15-34-28_custom-slash-commands-acp.md`