Add implementation plan for agent panel slash command menu

Nathan Sobo and Claude created

This plan outlines how to add a "/" command menu to the agent panel
that appears when users type "/" but only if the ACP agent supports
custom slash commands.

Key phases:
1. ACP Protocol Extension - Add new RPC methods and capabilities
2. Slash Command Menu UI - Add "/" detection and searchable menu
3. Agent Implementation Support - Help agents implement new methods

The plan includes proper protocol extension since we can modify the
agent-client-protocol crate, making the implementation much cleaner
with full type safety across the protocol boundary.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Change summary

thoughts/shared/plans/2025-08-28_20-04-42_agent-panel-slash-command-menu.md | 749 
thoughts/shared/research/2025-08-28_15-34-28_custom-slash-commands-acp.md   | 143 
2 files changed, 842 insertions(+), 50 deletions(-)

Detailed changes

thoughts/shared/plans/2025-08-28_20-04-42_agent-panel-slash-command-menu.md 🔗

@@ -0,0 +1,749 @@
+# Agent Panel Slash Command Menu Implementation Plan
+
+## Overview
+
+Add a searchable "/" command menu to the agent panel that appears when users type "/" - but only if the connected ACP agent supports custom slash commands. This enables users to discover and execute commands from `.claude/commands/` directory through a clean UI interface.
+
+## Current State Analysis
+
+**Agent Panel Message Editor**: Uses sophisticated completion system for "@" mentions with `PopoverMenu + Picker` pattern. Has existing slash command detection (`parse_slash_command()`) but only for highlighting/prevention, not menus.
+
+**ACP Capability System**: Well-established capability negotiation during `initialize()` with `PromptCapabilities`. UI adapts reactively via `AcpThreadEvent::PromptCapabilitiesUpdated`.
+
+**UI Patterns**: Perfect existing patterns in text thread slash command picker using `PopoverMenu + Picker` with `SlashCommandSelector` and `PickerDelegate` traits.
+
+**ACP Protocol**: Clear extension patterns via external `agent-client-protocol` crate with request/response enums and method dispatch.
+
+### Key Discoveries:
+- `crates/agent_ui/src/acp/message_editor.rs:1573` - Existing slash detection foundation
+- `crates/agent_ui/src/acp/completion_provider.rs:763` - Pattern for "@" completion triggers  
+- `crates/agent_ui/src/slash_command_picker.rs:54` - Exact UI pattern we need to follow
+- `crates/agent_servers/src/acp.rs:152` - Capability storage and distribution
+
+## Desired End State
+
+When user types "/" in agent panel message editor:
+- **Agent supports commands**: Searchable menu appears with commands from `.claude/commands/`
+- **Agent doesn't support commands**: No menu appears (preserves current behavior)
+- **Command execution**: Selected commands execute via ACP protocol, stream results to thread view
+- **Keyboard navigation**: Arrow keys, Enter to select, Escape to dismiss
+
+### Verification:
+- Menu appears only when `supports_custom_commands` capability is true
+- Commands populated from ACP `list_commands()` RPC call
+- Selected commands execute via ACP `run_command()` RPC call
+- Results stream back as `SessionUpdate` notifications
+
+## What We're NOT Doing
+
+- NOT modifying existing assistant/text thread slash commands
+- NOT implementing command parsing/execution logic in Zed (that's agent-side)
+- NOT adding command discovery beyond what agents provide
+- NOT changing the UI for agents that don't support custom commands
+
+## Implementation Approach
+
+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.
+
+## Phase 1: ACP Protocol Extension
+
+### Overview
+Add new slash command RPC methods and capabilities to the agent-client-protocol crate, then integrate them into Zed's ACP connection layer.
+
+### Changes Required:
+
+#### 1. Protocol Types (External Crate)
+**File**: `/Users/nathan/src/agent-client-protocol/rust/agent.rs`
+**Changes**: Add new request/response types and trait methods
+
+```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<Output = Result<ListCommandsResponse, Error>>;
+
+/// 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<Output = Result<(), Error>>;
+
+// Add around line 372 after PromptCapabilities
+/// Request parameters for listing available commands.
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+#[schemars(extend("x-side" = "agent", "x-method" = "session/list_commands"))]
+#[serde(rename_all = "camelCase")]
+pub struct ListCommandsRequest {
+    /// The session ID to list commands for.
+    pub session_id: SessionId,
+}
+
+/// Response containing available commands.
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+#[schemars(extend("x-side" = "agent", "x-method" = "session/list_commands"))]
+#[serde(rename_all = "camelCase")]
+pub struct ListCommandsResponse {
+    /// List of available commands.
+    pub commands: Vec<CommandInfo>,
+}
+
+/// Information about a custom command.
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CommandInfo {
+    /// Command name (e.g., "create_plan", "research_codebase").
+    pub name: String,
+    /// Human-readable description of what the command does.
+    pub description: String,
+    /// Whether this command requires arguments from the user.
+    pub requires_argument: bool,
+}
+
+/// Request parameters for executing a command.
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+#[schemars(extend("x-side" = "agent", "x-method" = "session/run_command"))]
+#[serde(rename_all = "camelCase")]
+pub struct RunCommandRequest {
+    /// The session ID to execute the command in.
+    pub session_id: SessionId,
+    /// Name of the command to execute.
+    pub command: String,
+    /// Optional arguments for the command.
+    pub args: Option<String>,
+}
+```
+
+#### 2. Capability Extension
+**File**: `/Users/nathan/src/agent-client-protocol/rust/agent.rs`
+**Changes**: Extend PromptCapabilities with custom command support
+
+```rust
+// Modify PromptCapabilities struct around line 358
+#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct PromptCapabilities {
+    /// Agent supports [`ContentBlock::Image`].
+    #[serde(default)]
+    pub image: bool,
+    /// Agent supports [`ContentBlock::Audio`].
+    #[serde(default)]
+    pub audio: bool,
+    /// Agent supports embedded context in `session/prompt` requests.
+    #[serde(default)]
+    pub embedded_context: bool,
+    /// Agent supports custom slash commands via `list_commands` and `run_command`.
+    #[serde(default)]
+    pub supports_custom_commands: bool,
+}
+```
+
+#### 3. AgentConnection Trait Extension
+**File**: `crates/acp_thread/src/connection.rs`
+**Changes**: Add new methods to trait definition
+
+```rust
+// Add these methods to AgentConnection trait around line 80
+fn list_commands(&self, session_id: &acp::SessionId, cx: &mut App) -> Task<Result<acp::ListCommandsResponse>>;
+fn run_command(&self, request: acp::RunCommandRequest, cx: &mut App) -> Task<Result<()>>;
+```
+
+#### 4. ACP Connection Implementation  
+**File**: `crates/agent_servers/src/acp.rs`
+**Changes**: Implement new trait methods in AcpConnection
+
+```rust
+// Add around line 340 after existing method implementations
+fn list_commands(&self, session_id: &acp::SessionId, cx: &mut App) -> Task<Result<acp::ListCommandsResponse>> {
+    let conn = self.connection.clone();
+    let session_id = session_id.clone();
+    cx.foreground_executor().spawn(async move {
+        conn.list_commands(acp::ListCommandsRequest { session_id }).await
+    })
+}
+
+fn run_command(&self, request: acp::RunCommandRequest, cx: &mut App) -> Task<Result<()>> {
+    let conn = self.connection.clone();
+    cx.foreground_executor().spawn(async move {
+        conn.run_command(request).await
+    })
+}
+```
+
+#### 5. Capability Detection
+**File**: `crates/acp_thread/src/acp_thread.rs`
+**Changes**: Add capability checking helper
+
+```rust
+// Add around line 280 after other capability methods
+pub fn supports_custom_commands(&self, cx: &App) -> bool {
+    self.prompt_capabilities.get(cx).supports_custom_commands
+}
+```
+
+### Success Criteria:
+
+#### Automated Verification:
+- [ ] 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`
+- [ ] Zed compiles successfully: `./script/clippy`
+- [ ] No linting errors: `cargo clippy --package agent_servers --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
+
+---
+
+## Phase 2: Slash Command Menu UI
+
+### Overview
+Add "/" detection and command menu to the ACP message editor, following the existing "@" completion pattern.
+
+### Changes Required:
+
+#### 1. Command Info Types
+**File**: `crates/agent_ui/src/acp/completion_provider.rs`
+**Changes**: Add command completion types
+
+```rust
+// Add around line 50 after existing completion types
+#[derive(Debug, Clone)]
+pub struct SlashCommandCompletion {
+    pub name: String,
+    pub description: String,
+    pub requires_argument: bool,
+    pub source_range: Range<usize>,
+    pub command_range: Range<usize>,
+}
+
+impl SlashCommandCompletion {
+    fn try_parse(line: &str, cursor_offset: usize) -> Option<Self> {
+        // Parse "/" followed by optional command name
+        if let Some(remainder) = line.strip_prefix('/') {
+            let mut chars = remainder.char_indices().peekable();
+            let mut command_end = 0;
+            
+            // Find end of command name (alphanumeric + underscore)
+            while let Some((i, ch)) = chars.next() {
+                if ch.is_alphanumeric() || ch == '_' {
+                    command_end = i + ch.len_utf8();
+                } else {
+                    break;
+                }
+            }
+            
+            Some(SlashCommandCompletion {
+                name: remainder[..command_end].to_string(),
+                description: String::new(),
+                requires_argument: false,
+                source_range: 0..cursor_offset,
+                command_range: 1..command_end + 1, // Skip the "/"
+            })
+        } else {
+            None
+        }
+    }
+}
+```
+
+#### 2. Completion Trigger Detection
+**File**: `crates/agent_ui/src/acp/completion_provider.rs`
+**Changes**: Extend `is_completion_trigger()` method
+
+```rust
+// Modify around line 763 to add slash command detection
+pub fn is_completion_trigger(
+    &self,
+    buffer: &Entity<Buffer>,
+    position: language::Anchor,
+    text: &str,
+    _trigger_in_words: bool,
+    cx: &mut Context<Editor>,
+) -> 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;
+            }
+        }
+    }
+    
+    false
+}
+```
+
+#### 3. Command Completion Generation
+**File**: `crates/agent_ui/src/acp/completion_provider.rs`
+**Changes**: Extend `completions()` method
+
+```rust
+// Add around line 700 in completions() method after mention handling
+// Handle slash command completions
+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) {
+            return self.complete_slash_commands(
+                slash_completion,
+                buffer.clone(),
+                cursor_anchor,
+                cx,
+            );
+        }
+    }
+}
+
+// Add new method around line 850
+fn complete_slash_commands(
+    &self,
+    completion: SlashCommandCompletion,
+    buffer: Entity<Buffer>,
+    cursor_anchor: language::Anchor,
+    cx: &mut Context<Editor>,
+) -> Task<Result<Vec<project::CompletionResponse>>> {
+    let Some(thread) = self.thread.clone() else {
+        return Task::ready(Ok(Vec::new()));
+    };
+    
+    cx.spawn(async move |cx| {
+        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?;
+        
+        // Filter commands matching typed prefix
+        let matching_commands: Vec<_> = commands
+            .into_iter()
+            .filter(|cmd| cmd.name.starts_with(&completion.name))
+            .collect();
+        
+        // Convert to completion responses
+        let mut completions = Vec::new();
+        for command in matching_commands {
+            let new_text = format!("/{}", command.name);
+            let completion = project::Completion {
+                old_range: completion.source_range.clone(),
+                new_text,
+                label: command.name.clone().into(),
+                server_id: language::LanguageServerId(0), // Not from language server
+                kind: Some(language::CompletionKind::Function),
+                documentation: if !command.description.is_empty() {
+                    Some(language::Documentation::SingleLine(command.description.clone()))
+                } else {
+                    None
+                },
+                confirm: Some(Arc::new(SlashCommandConfirmation {
+                    command: command.name,
+                    requires_argument: command.requires_argument,
+                    thread: thread.downgrade(),
+                })),
+                ..Default::default()
+            };
+            completions.push(completion);
+        }
+        
+        Ok(vec![project::CompletionResponse {
+            completions,
+            is_incomplete: false,
+        }])
+    })
+}
+```
+
+#### 4. Command Confirmation Handler
+**File**: `crates/agent_ui/src/acp/completion_provider.rs`
+**Changes**: Add confirmation handler for slash commands
+
+```rust
+// Add around line 950
+#[derive(Debug)]
+struct SlashCommandConfirmation {
+    command: String,
+    requires_argument: bool,
+    thread: WeakEntity<AcpThread>,
+}
+
+impl language::CompletionConfirm for SlashCommandConfirmation {
+    fn confirm(
+        &self,
+        completion: &project::Completion,
+        buffer: &mut Buffer,
+        mut cursor_positions: Vec<language::Anchor>,
+        trigger_text: &str,
+        _workspace: Option<&Workspace>,
+        window: &mut Window,
+        cx: &mut Context<Buffer>,
+    ) -> Option<Task<Result<Vec<language::Anchor>>>> {
+        if self.requires_argument {
+            // Keep cursor after command name for argument input
+            return None; // Let default behavior handle text insertion
+        }
+        
+        // Execute command immediately
+        let Some(thread) = self.thread.upgrade() else {
+            return None;
+        };
+        
+        let command = self.command.clone();
+        let task = cx.spawn(async move |cx| {
+            thread
+                .update(cx, |thread, cx| {
+                    thread.run_command(command, None, cx)
+                })
+                .ok();
+            Ok(cursor_positions)
+        });
+        
+        Some(task)
+    }
+}
+```
+
+#### 5. Command Execution Method
+**File**: `crates/acp_thread/src/acp_thread.rs`
+**Changes**: Add command execution method
+
+```rust
+// Add around line 450 after other public methods
+pub fn run_command(
+    &mut self,
+    command: String,
+    args: Option<String>,
+    cx: &mut Context<Self>,
+) -> Task<Result<()>> {
+    let session_id = self.session_id.clone();
+    let connection = self.connection.clone();
+    
+    cx.spawn(async move |this, cx| {
+        let request = acp::RunCommandRequest {
+            session_id,
+            command,
+            args,
+        };
+        
+        connection.run_command(request, cx).await?;
+        
+        // The agent will send back results via SessionUpdate notifications
+        // which will be handled by existing handle_session_update() logic
+        Ok(())
+    })
+}
+```
+
+### Success Criteria:
+
+#### Automated Verification:
+- [ ] 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`
+
+#### 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
+
+---
+
+## Phase 3: Agent Implementation Support
+
+### Overview
+Prepare the Claude Code ACP adapter to implement the new slash command RPC methods by adding command parsing and execution.
+
+### Changes Required:
+
+#### 1. Command Parsing Module
+**File**: `claude-code-acp/src/command-parser.ts` (new file)
+**Changes**: Add markdown command parser
+
+```typescript
+import * as fs from 'fs';
+import * as path from 'path';
+
+export interface CommandInfo {
+  name: string;
+  description: string;
+  requires_argument: boolean;
+  content?: string; // Full command content for execution
+}
+
+export class CommandParser {
+  private commandsDir: string;
+  private cachedCommands?: CommandInfo[];
+
+  constructor(cwd: string) {
+    this.commandsDir = path.join(cwd, '.claude', 'commands');
+  }
+
+  async listCommands(): Promise<CommandInfo[]> {
+    if (this.cachedCommands) {
+      return this.cachedCommands;
+    }
+
+    try {
+      if (!fs.existsSync(this.commandsDir)) {
+        return [];
+      }
+
+      const files = fs.readdirSync(this.commandsDir)
+        .filter(file => file.endsWith('.md'));
+
+      const commands: CommandInfo[] = [];
+      for (const file of files) {
+        const filePath = path.join(this.commandsDir, file);
+        const content = fs.readFileSync(filePath, 'utf-8');
+        const commandInfo = this.parseCommandFile(content, file);
+        if (commandInfo) {
+          commands.push(commandInfo);
+        }
+      }
+
+      this.cachedCommands = commands;
+      return commands;
+    } catch (error) {
+      console.error('Failed to list commands:', error);
+      return [];
+    }
+  }
+
+  private parseCommandFile(content: string, filename: string): CommandInfo | null {
+    const lines = content.split('\n');
+    let name = '';
+    let description = '';
+    let requires_argument = false;
+
+    // Extract command name from H1 title
+    const titleMatch = lines.find(line => line.startsWith('# '));
+    if (titleMatch) {
+      name = titleMatch.replace('# ', '').trim().toLowerCase().replace(/\s+/g, '_');
+    } else {
+      // Fall back to filename without extension
+      name = path.basename(filename, '.md');
+    }
+
+    // Extract description (text after H1, before first H2)
+    const titleIndex = lines.findIndex(line => line.startsWith('# '));
+    if (titleIndex >= 0) {
+      const nextHeaderIndex = lines.findIndex((line, i) => 
+        i > titleIndex && line.startsWith('## '));
+      const endIndex = nextHeaderIndex >= 0 ? nextHeaderIndex : lines.length;
+      
+      description = lines
+        .slice(titleIndex + 1, endIndex)
+        .join('\n')
+        .trim()
+        .split('\n')[0] || ''; // First non-empty line as description
+    }
+
+    // Check if command requires arguments (heuristic)
+    requires_argument = content.includes('arguments') || 
+                      content.includes('parameter') ||
+                      content.includes('[arg]') ||
+                      content.includes('{arg}');
+
+    return {
+      name,
+      description,
+      requires_argument,
+      content
+    };
+  }
+
+  async getCommand(name: string): Promise<CommandInfo | null> {
+    const commands = await this.listCommands();
+    return commands.find(cmd => cmd.name === name) || null;
+  }
+
+  // Clear cache when commands directory changes
+  invalidateCache(): void {
+    this.cachedCommands = undefined;
+  }
+}
+```
+
+#### 2. ACP Agent Method Implementation
+**File**: `claude-code-acp/src/acp-agent.ts`
+**Changes**: Add new RPC method handlers
+
+```typescript
+// Add import
+import { CommandParser, CommandInfo } from './command-parser';
+
+// Add to ClaudeAcpAgent class around line 50
+private commandParser?: CommandParser;
+
+// 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);
+  }
+}
+
+// Add capability declaration in initialize() around line 150
+async initialize(request: InitializeRequest): Promise<InitializeResponse> {
+  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'],
+  };
+}
+
+// Add new RPC method handlers around line 400
+async listCommands(request: ListCommandsRequest): Promise<ListCommandsResponse> {
+  if (!this.commandParser) {
+    return { commands: [] };
+  }
+
+  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: [] };
+  }
+}
+
+async runCommand(request: RunCommandRequest): Promise<void> {
+  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}`);
+  }
+
+  // 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 {
+    let systemPrompt = command.content;
+    
+    // If command requires arguments and args provided, append them
+    if (command.requires_argument && request.args) {
+      systemPrompt += `\n\nArguments: ${request.args}`;
+    }
+
+    // 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,
+      },
+    });
+
+    // 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);
+    }
+
+  } 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}`,
+      },
+    });
+  }
+}
+```
+
+### 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` 
+
+#### 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
+
+---
+
+## Testing Strategy
+
+### Unit Tests:
+- Command parser correctly extracts name, description, and argument requirements
+- Slash completion parsing handles various input formats
+- Capability detection works with different agent configurations
+
+### Integration Tests:  
+- End-to-end slash command flow from "/" keystroke to command execution
+- Menu appearance/dismissal based on agent capabilities
+- Command completion filtering and selection
+
+### Manual Testing Steps:
+1. Connect to agent without custom command support → verify no "/" menu
+2. Connect to agent with custom command support → verify "/" shows menu
+3. Type "/cr" → verify "create_plan" command appears in filtered list
+4. Select command with arguments → verify argument input continues
+5. Select command without arguments → verify immediate execution
+6. Press Escape during menu → verify menu dismisses
+7. Click outside menu → verify menu dismisses
+
+## Performance Considerations
+
+- Command list caching in agent to avoid repeated filesystem reads
+- Debounced completion triggers to avoid excessive RPC calls  
+- Async command execution to prevent UI blocking
+- Menu virtualization for large command lists (if needed)
+
+## Migration Notes
+
+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.
+
+## References
+
+- Original research: `thoughts/shared/research/2025-08-28_15-34-28_custom-slash-commands-acp.md`
+- Text thread slash command picker: `crates/agent_ui/src/slash_command_picker.rs:54-348`
+- ACP completion provider: `crates/agent_ui/src/acp/completion_provider.rs:763`
+- Agent capability negotiation: `crates/agent_servers/src/acp.rs:131-156`

thoughts/shared/research/2025-08-28_15-34-28_custom-slash-commands-acp.md 🔗

@@ -21,7 +21,7 @@ last_updated_by: Mikayla Maki
 
 ## Research Question
 
-We're adding support for custom slash commands to Agent Client Protocol. The client should be able to:
+We're adding support for custom slash commands to Agent Client Protocol for the **agent panel** (not assistant 1/text threads). The client should be able to:
 
 - List available commands
 - Run a command with arguments (check Claude Code behavior)
@@ -34,44 +34,55 @@ In the Claude Code ACP adapter, we want implement the _agent_ side of the protoc
 We need to update the protocol to support the new RPCs for listing and running commands.
 We need to understand how to run commands via the SDK.
 
+**Important Note**: This is for the agent panel UX, NOT the existing assistant/text thread slash commands. The existing slash command infrastructure is for assistant 1/text threads and is not relevant to this implementation.
+
 ## Summary
 
-The research reveals a comprehensive architecture for implementing custom slash commands in ACP:
+The research reveals the architecture needed for implementing custom slash commands in the **agent panel** via ACP:
 
-**Claude Code Slash Commands**: Well-established system with 15+ built-in commands, extensible architecture, and consistent execution patterns through the `SlashCommand` trait.
+**Agent Panel Architecture**: Separate UI system from assistant/text threads with dedicated components (`AgentPanel`, `AcpThreadView`) and message handling through ACP protocol integration.
 
-**ACP Protocol**: JSON-RPC based with clear patterns for adding new RPC methods through request/response enums, method dispatch, and capability negotiation.
+**ACP Protocol**: JSON-RPC based with clear patterns for adding new RPC methods through request/response enums, method dispatch, and capability negotiation. Handles session management, tool calls, and real-time message streaming.
 
 **Claude Commands Structure**: Markdown-based command definitions in `.claude/commands/` with consistent format, metadata, and programmatic parsing potential.
 
 **SDK Integration**: Claude Code ACP adapter bridges ACP protocol with Claude SDK, providing tool execution and session management through MCP servers.
 
+**Note**: The existing Claude Code slash command system (`SlashCommand` trait, `assistant_slash_command` crate) is **not relevant** - that's for assistant 1/text threads. The agent panel needs its own custom command implementation.
+
 ## Detailed Findings
 
-### Claude Code Slash Command Architecture
+### Agent Panel Architecture
+
+**Core Infrastructure** (`crates/agent_ui/`):
 
-**Core Infrastructure** (`crates/assistant_slash_command/`):
+- `agent_panel.rs:24` - Main `AgentPanel` struct and UI component
+- `acp/thread_view.rs:315` - `AcpThreadView` component for individual agent conversations
+- `acp/message_editor.rs` - Message input component with slash command integration for agent panel
+- `acp.rs` - ACP module entry point connecting to external ACP agents
 
-- `assistant_slash_command.rs:1-200` - `SlashCommand` trait with `name()`, `description()`, `run()`, `complete_argument()`, `requires_argument()` methods
-- `slash_command_registry.rs:1-150` - Global registry for command registration and lookup
-- `slash_command_working_set.rs:1-100` - Working set management for active commands
-- `extension_slash_command.rs:1-250` - Extension-based command support
+**Agent Panel vs Assistant Distinction**:
 
-**Execution Flow**:
+The agent panel is **completely separate** from the assistant/text thread system:
+- Agent panel uses ACP (Agent Client Protocol) for external agent communication
+- Assistant uses internal Zed slash commands and text thread editors
+- Different UI components, different input handling, different protocol integration
 
-1. Commands registered in `agent_ui.rs:403-448` via `slash_command_registry.register_command()`
-2. User types `/command args` in editor
-3. `SlashCommandCompletionProvider` provides completions
-4. `TextThreadEditor.run_command()` calls `command.run()`
-5. Commands return stream of `SlashCommandEvent`s
-6. Events processed and inserted into context
+**ACP Integration Flow**:
 
-**Built-in Commands** (`crates/assistant_slash_commands/`):
+1. External ACP agent process spawned via `agent_servers/src/acp.rs:63-76`
+2. JSON-RPC connection established over stdin/stdout at line 84
+3. Protocol initialization with capability negotiation at line 131
+4. Sessions created via `new_session()` request for isolated conversations
+5. User input converted to `PromptRequest` and sent to ACP agent
+6. Agent responses stream back as `SessionUpdate` notifications
+7. UI updates processed in `AcpThread::handle_session_update()`
 
-- File operations: `/file`, `/tab`, `/selection`
-- Development: `/cargo-workspace`, `/diagnostics`, `/symbols`
-- Context: `/context-server`, `/fetch`, `/prompt`
-- Utility: `/now`, `/delta`
+**Current Input Handling**:
+
+- Message composition through `MessageEditor` and specialized ACP message editor
+- Standard chat input without custom command support currently
+- Integration with model selector, context strip, and profile management
 
 ### Agent Client Protocol RPC Patterns
 
@@ -168,8 +179,11 @@ let q = query({
 
 ## Code References
 
-- `crates/assistant_slash_command/src/assistant_slash_command.rs:1-200` - Core SlashCommand trait
-- `crates/agent_ui/src/agent_ui.rs:403-448` - Command registration point
+- `crates/agent_ui/src/agent_panel.rs:24` - Main AgentPanel component
+- `crates/agent_ui/src/acp/thread_view.rs:315` - AcpThreadView UI component
+- `crates/agent_ui/src/acp/message_editor.rs` - Agent panel message input
+- `crates/agent_servers/src/acp.rs:63-162` - ACP connection establishment
+- `crates/acp_thread/src/acp_thread.rs:826` - ACP thread creation
 - `agentic-coding-protocol/rust/agent.rs:604-610` - ACP request enum pattern
 - `agentic-coding-protocol/rust/acp.rs:355-371` - Method dispatch logic
 - `claude-code-acp/src/acp-agent.ts:1-500` - ACP adapter implementation
@@ -177,19 +191,19 @@ let q = query({
 
 ## Architecture Insights
 
-**Slash Command System**: Highly modular with clear trait-based abstraction, supports both built-in and extension commands, uses streaming execution model with event-based progress reporting.
+**Agent Panel System**: Completely separate from assistant/text threads, uses ACP protocol for external agent communication with JSON-RPC over stdin/stdout, manages sessions with unique IDs, and provides real-time message streaming with UI updates.
 
-**ACP Protocol**: Designed for extensibility with capability negotiation, type safety through Rust enums, symmetric bidirectional design, and JSON-RPC foundation.
+**ACP Protocol**: Designed for extensibility with capability negotiation, type safety through Rust enums, symmetric bidirectional design, and JSON-RPC foundation. Handles tool calls, permissions, and session management.
 
-**Command Definitions**: Human-readable markdown with programmatically parseable structure, consistent metadata patterns, and workflow automation framework.
+**Command Definitions**: Human-readable markdown with programmatically parseable structure, consistent metadata patterns, and workflow automation framework stored in `.claude/commands/`.
 
-**Integration Patterns**: Claude Code ACP adapter provides proven pattern for bridging protocols, MCP servers enable tool execution proxying, session management handles concurrent interactions.
+**Integration Patterns**: Claude Code ACP adapter provides proven pattern for bridging protocols, MCP servers enable tool execution proxying, session management handles concurrent interactions. Agent panel needs new command integration separate from existing slash commands.
 
 ## Implementation Recommendations
 
 ### 1. Protocol Extension for Custom Commands
 
-Add new RPC methods to ACP schema:
+Add new RPC methods to ACP schema following existing patterns in `agentic-coding-protocol/rust/`:
 
 ```rust
 // New request types
@@ -215,9 +229,23 @@ pub struct CommandInfo {
 }
 ```
 
-### 2. Agent Implementation
+Add to request/response enums and implement in dispatch logic similar to existing ACP methods.
+
+### 2. Agent Panel UI Integration
+
+**Option A**: Extend ACP Message Editor
+- Modify `crates/agent_ui/src/acp/message_editor.rs` to detect custom commands
+- Add command completion/suggestion UI similar to existing patterns
+- Trigger custom command execution through ACP protocol
 
-In Claude Code ACP adapter:
+**Option B**: New Command Interface
+- Create dedicated command input component in agent panel
+- Separate from regular message input to provide distinct UX
+- Integrate with `AcpThreadView` for command results display
+
+### 3. ACP Agent Implementation
+
+In Claude Code ACP adapter (`claude-code-acp/src/acp-agent.ts`):
 
 ```typescript
 async listCommands(request: ListCommandsRequest): Promise<ListCommandsResponse> {
@@ -229,11 +257,11 @@ async listCommands(request: ListCommandsRequest): Promise<ListCommandsResponse>
 async runCommand(request: RunCommandRequest): Promise<RunCommandResponse> {
   // Find command definition in .claude/commands/
   // Execute via Claude SDK query with command content
-  // Stream results back via session notifications
+  // Stream results back via session notifications using existing session update mechanism
 }
 ```
 
-### 3. Command Parsing
+### 4. Command Parsing and Execution
 
 Implement markdown parser for `.claude/commands/*.md`:
 
@@ -246,37 +274,44 @@ function parseCommandFile(content: string): CommandInfo {
 }
 ```
 
+Execute commands by sending command content as system prompt to Claude SDK, similar to existing ACP query patterns.
+
 ## Open Questions
 
-1. **Command Arguments**: How should complex command arguments be structured and validated?
+1. **Agent Panel UX**: Should custom commands be integrated into the existing message input or as a separate command interface?
 
-2. **Command Context**: Should commands have access to current session state, file context, or conversation history?
+2. **Command Arguments**: How should complex command arguments be structured and validated in the agent panel context?
 
-3. **Command Discovery**: Should commands be cached or re-read on each listing request?
+3. **Command Context**: Should commands have access to current ACP session state, file context, or conversation history?
 
-4. **Extension Commands**: How should extension-provided commands integrate with `.claude/commands/` file-based commands?
+4. **Command Discovery**: Should commands be cached or re-read on each listing request? How does this integrate with ACP session lifecycle?
 
-5. **Command Execution**: Should commands run in isolated contexts or share session state?
+5. **Command Execution**: Should commands run in isolated contexts or share ACP session state?
 
-6. **Error Handling**: What's the appropriate error handling strategy for command parsing and execution failures?
+6. **Error Handling**: What's the appropriate error handling strategy for command parsing and execution failures in the agent panel?
+
+7. **UI Integration**: How should command execution progress and results be displayed within the `AcpThreadView` component?
 
 ###### FINAL CLAUDE PROMPT:
 
 ⏺ I've completed comprehensive research on adding custom
-slash commands to the Agent Client Protocol. Here's what
+slash commands to the Agent Client Protocol for the
+**agent panel** (NOT assistant/text threads). Here's what
 I found:
 
 Key Findings
 
-Slash Command Architecture: Claude Code has a
-well-established slash command system with 15+ built-in
-commands using a SlashCommand trait. Commands are
-registered globally and execute through streaming events.
+Agent Panel Architecture: The agent panel is completely
+separate from assistant/text threads, using ACP protocol
+for external agent communication via JSON-RPC over
+stdin/stdout. It has dedicated UI components like
+AgentPanel and AcpThreadView.
 
 ACP Protocol Patterns: The protocol uses JSON-RPC with
 type-safe request/response enums. Adding new RPC methods
 follows a clear pattern: define structs, add to enums,
-update dispatch logic.
+update dispatch logic. Handles sessions, tool calls, and
+real-time streaming.
 
 .claude/commands Structure: Commands are defined as
 structured Markdown files with consistent format -
@@ -287,19 +322,27 @@ SDK Integration: The Claude Code ACP adapter already
 bridges ACP with the Claude SDK through MCP servers,
 providing a proven pattern for command execution.
 
+**IMPORTANT**: The existing Claude Code slash command
+system (SlashCommand trait, assistant_slash_command crate)
+is NOT relevant - that's for assistant 1/text threads.
+The agent panel needs its own custom command
+implementation.
+
 Implementation Approach
 
 You'll need to:
 
 1. Extend ACP Protocol: Add ListCommandsRequest/Response
    and RunCommandRequest/Response to the schema
-2. Parse Command Definitions: Read .claude/commands/\*.md
+2. Agent Panel UI: Integrate custom commands into agent
+   panel message input or create separate command interface
+3. Parse Command Definitions: Read .claude/commands/\*.md
    files and extract metadata (name, description,
    requirements)
-3. Execute Commands: Run commands via Claude SDK queries,
-   similar to how the current ACP adapter works
-4. Handle Streaming: Use ACP's session notification
-   system to stream command progress
+4. Execute Commands: Run commands via Claude SDK queries
+   through existing ACP session mechanism
+5. Handle Streaming: Use ACP's session notification
+   system to stream command progress to AcpThreadView
 
 The research document is saved at thoughts/shared/researc
 h/2025-08-28_15-34-28_custom-slash-commands-acp.md with