Feature Design: Agent Mode Switching
Original Proposal
Add a left button with $ icon (depicting terminal command execution). When clicked, user can see icons for agents they can @ for a message. The agent can simply be Claude Code. That means if user switches from $ icon to Claude Code icon, whatever is put in the input box will be the input to Claude Code in non-interactive mode, in which Claude will reply in stdout like any other commands are executed.
Potential Issues & Better Approaches
Issue 1: Claude Code is already running (this conversation!)
- Claude Code is the AI assistant you're talking to RIGHT NOW
- It can't execute commands on itself in non-interactive mode while also being interactive
- Running
claudeCLI would spawn a separate instance
Issue 2: Context & Session Management
- Each
claudeCLI invocation would be stateless (no memory of previous messages) - You'd lose the conversation context
- VS Code/Claude Desktop handle this with persistent sessions
Issue 3: Output Format Mismatch
- Claude Code in non-interactive mode outputs raw text
- Your UI is designed for command stdout/stderr streams
- Claude responses are conversational, not command output
Better Feature Design Options
Option A: Dual-Mode Input (Recommended)
┌─────────────────────────────────────────┐
│ [$] [🤖] [Input box] [Send]│
└─────────────────────────────────────────┘
$ mode (default): Command execution
🤖 mode: AI chat (Claude/agents)
How it works:
- User toggles between
$(terminal) and🤖(AI chat) - In
$mode: Backend executes commands via spawn - In
🤖mode: Backend routes to AI API (Claude API, local LLM, etc.) - Messages render differently based on mode:
- Terminal: Code block with stdout/stderr
- AI: Conversational bubbles with markdown
Benefits:
- Clear mode separation
- Can integrate Claude API, local LLMs, or other AI agents
- Existing message UI can handle both (just different styling)
Drawbacks:
- Requires mode switching (extra click)
- Can't mix terminal commands and AI in same message
- Need to remember which mode you're in
Option B: @ Mentions (Like Slack/Discord)
Input: "@claude-code how do I fix this error?"
OR
Input: "ls -la" (no @, runs as command)
How it works:
- Parse input for
@agent-name - If detected → route to that agent
- No
@→ execute as terminal command - Agents can be:
@claude-code,@copilot,@local-llm, etc.
Benefits:
- Familiar pattern (Slack, GitHub, Discord)
- Can mix: "git status" then "@claude-code explain this error"
- No UI mode switching needed
- Very lightweight implementation
Drawbacks:
- Less discoverable (users might not know about @ mentions)
- Harder to show which agents are available
- Could conflict with actual @ in commands (edge case)
Option C: Agent Selection Dropdown
┌────────────┬─────────────────────────────┬──────┐
│ [Terminal ▾] │ [Input box] │[Send]│
│ ├─ Terminal │ │ │
│ ├─ Claude │ │ │
│ └─ Copilot │ │ │
└──────────────┴─────────────────────────────┴──────┘
How it works:
- Dropdown shows available "executors"
- Terminal: Runs bash commands
- Claude/Copilot/etc: Sends to AI agent APIs
- Selection persists until changed
Benefits:
- Explicit target selection
- Easy to add more agents
- Clean UI
- Very discoverable
Drawbacks:
- Takes up more horizontal space
- Dropdown might be slower to use than toggle
- Still requires switching for mixed usage
Option D: Hybrid Approach (@ Mentions + Mode Indicator)
┌─────────────────────────────────────────┐
│ [$] [Input box] [Send]│
│ "@claude-code ..." │
└─────────────────────────────────────────┘
Mode indicator changes when @ detected:
[$] → [🤖 claude-code]
How it works:
- Default mode is terminal (
$icon) - As user types
@agent-name, mode indicator updates - If no
@, execute as terminal command - If
@detected, route to that agent - Autocomplete agents when typing
@
Benefits:
- Best of both worlds (discoverability + ease of use)
- Visual feedback on what will execute
- No explicit mode switching required
- Autocomplete makes agents discoverable
Drawbacks:
- Slightly more complex to implement
- Need to handle @ parsing and autocomplete
Technical Architecture (Regardless of UI)
Backend Changes
// server.js - New unified endpoint
socket.on('execute-message', ({ repoPath, content, targetAgent, jobId }) => {
if (targetAgent === 'terminal' || targetAgent === '$') {
// Existing command execution
const childProcess = spawn('/bin/bash', ['-l', '-c', content], {
cwd: repoPath,
env: process.env
});
// Stream stdout/stderr
childProcess.stdout.on('data', (data) => {
socket.emit('output', { jobId, data: data.toString(), stream: 'stdout' });
});
childProcess.stderr.on('data', (data) => {
socket.emit('output', { jobId, data: data.toString(), stream: 'stderr' });
});
childProcess.on('close', (code, signal) => {
socket.emit('job-complete', { jobId, exitCode: code, signal });
});
}
else if (targetAgent === 'claude-code' || targetAgent === 'claude') {
// Route to Claude API
try {
const response = await callClaudeAPI({
message: content,
context: {
repoPath,
// Could include repo files, git status, etc.
}
});
socket.emit('ai-response', {
jobId,
content: response,
agent: 'claude-code'
});
} catch (error) {
socket.emit('job-error', { jobId, error: error.message });
}
}
else if (targetAgent === 'local-llm') {
// Route to local LLM (Ollama, llama.cpp, etc.)
const response = await callLocalLLM(content, repoPath);
socket.emit('ai-response', { jobId, content: response, agent: 'local-llm' });
}
else {
socket.emit('job-error', {
jobId,
error: `Unknown agent: ${targetAgent}`
});
}
});
Frontend Changes
// types/index.ts
export type ExecutionMode = 'terminal' | 'claude-code' | 'local-llm';
export interface AgentConfig {
id: string;
name: string;
icon: string;
description: string;
requiresApiKey?: boolean;
}
// ChatContext.tsx
const [executionMode, setExecutionMode] = useState<ExecutionMode>('terminal');
const availableAgents: AgentConfig[] = [
{
id: 'terminal',
name: 'Terminal',
icon: '$',
description: 'Execute bash commands'
},
{
id: 'claude-code',
name: 'Claude Code',
icon: '🤖',
description: 'AI assistant for coding',
requiresApiKey: true
},
{
id: 'local-llm',
name: 'Local LLM',
icon: '🧠',
description: 'Run local AI model'
}
];
const sendMessage = (content: string) => {
// Option B: Parse @ mentions
const agentMatch = content.match(/^@([\w-]+)\s+(.+)/);
const targetAgent = agentMatch ? agentMatch[1] : executionMode;
const actualContent = agentMatch ? agentMatch[2] : content;
socket.emit('execute-message', {
repoPath: currentRepository.path,
content: actualContent,
targetAgent,
jobId: generateJobId()
});
};
New Message Types
// types/index.ts
export interface AIMessage extends BaseMessage {
type: MessageType.AI;
agent: string;
content: string;
// AI messages don't have exitCode, just content
}
export interface TerminalMessage extends BaseMessage {
type: MessageType.JOB;
command: string;
output: string;
exitCode?: number;
status: JobStatus;
}
UI Component Changes
// components/MessageBubble.tsx
const MessageBubble = ({ message }: { message: Message }) => {
if (message.type === MessageType.AI) {
return (
<div className="ai-message-bubble">
<div className="agent-badge">{message.agent}</div>
<ReactMarkdown>{message.content}</ReactMarkdown>
</div>
);
}
if (message.type === MessageType.JOB) {
return (
<div className="terminal-message">
<pre className="command">$ {message.command}</pre>
<pre className="output">{message.output}</pre>
{message.exitCode !== undefined && (
<div className="exit-code">Exit code: {message.exitCode}</div>
)}
</div>
);
}
// ... existing message types
};
Implementation Recommendations
Phase 1: MVP (Start with Option B - @ Mentions)
Why:
- Simplest to implement - Just parse input, no UI changes
- Works with existing UI - Terminal messages already render properly
- Lightweight - Can test concept without major refactoring
Steps:
- Add
@mention parsing in frontend (sendMessage) - Add agent routing in backend (
execute-messagehandler) - Integrate with Claude API (or local LLM for testing)
- Render AI responses as special message type
Code Changes:
- Frontend: ~50 lines (parsing + socket emit)
- Backend: ~100 lines (routing + API integration)
- No UI changes required
Phase 2: Enhanced UX (Add Option A - Mode Toggle)
After MVP works, add:
- Toggle button in input area
- Visual mode indicator
- Agent selection UI (show available agents)
UI Additions:
// ChatPage.tsx
<div className="input-container">
<button
onClick={() => toggleMode()}
className="mode-toggle"
>
{executionMode === 'terminal' ? '$' : '🤖'}
</button>
<textarea
placeholder={
executionMode === 'terminal'
? 'Enter command...'
: 'Ask Claude...'
}
/>
<button onClick={sendMessage}>Send</button>
</div>
Phase 3: Advanced Features
- Agent autocomplete - When typing
@, show dropdown of available agents - Context awareness - AI knows current repo, git status, recent commands
- Multi-turn conversations - AI maintains context across messages
- Streaming responses - Show AI typing in real-time
- Agent marketplace - Add custom agents (GitHub Copilot, local models, etc.)
Claude API Integration
Option 1: Direct Anthropic API
// server.js
const Anthropic = require('@anthropic-ai/sdk');
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
async function callClaudeAPI({ message, context }) {
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
messages: [
{
role: 'user',
content: `Repository: ${context.repoPath}\n\n${message}`
}
]
});
return response.content[0].text;
}
Pros:
- Official API, most reliable
- Best model quality
- Streaming support
Cons:
- Requires API key (costs money)
- Network latency
- Rate limits
Option 2: Local LLM (Ollama)
// server.js
async function callLocalLLM(message, repoPath) {
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'codellama',
prompt: `Repository: ${repoPath}\n\n${message}`,
stream: false
})
});
const data = await response.json();
return data.response;
}
Pros:
- Free (after initial setup)
- No network latency
- Privacy (data stays local)
- Works offline
Cons:
- Requires local Ollama installation
- Lower quality than Claude
- Requires GPU for good performance
UX Flow Examples
Example 1: Terminal Command
User types: ls -la
Mode: $ (terminal)
Result: Terminal output with file list
Example 2: AI Query (@ mention)
User types: @claude-code explain this error: "npm ERR! code ENOENT"
Mode: $ (terminal, but @ detected)
Result: AI response explaining the error
Example 3: AI Query (mode toggle)
User clicks: 🤖 icon
User types: how do I fix this git conflict?
Mode: 🤖 (AI)
Result: AI response with git conflict resolution steps
Example 4: Mixed Usage
1. User: git status
→ Terminal output
2. User: @claude-code what do these changes mean?
→ AI analyzes git status and explains
3. User: git add .
→ Terminal output
4. User: @claude-code write a commit message
→ AI generates commit message
Questions to Answer Before Implementation
Which UI approach do you prefer?
- Option A: Toggle button (
$⇄🤖) - Option B: @ mentions only
- Option C: Dropdown selector
- Option D: Hybrid (@ mentions + visual indicator)
- Option A: Toggle button (
Do you have Claude API access?
- Yes → Use Anthropic API
- No → Start with local LLM (Ollama)
- Maybe → Implement both, let user choose
Should AI responses be conversational or tool-focused?
- Conversational: "This error means X. To fix it, try Y."
- Tool-focused: Just return the command to run
- Both: Let user configure preference
Per-repo or global agent selection?
- Global: Same agent across all repos
- Per-repo: Different repos might use different agents
- Smart: Remember last-used agent per repo
Context awareness level?
- Minimal: Just the user's message
- Medium: Include repo path, current directory
- Full: Include git status, recent commands, file tree
- Maximum: Full codebase analysis (expensive)
Message history for AI?
- Stateless: Each AI call is independent
- Session-based: Maintain conversation within current session
- Persistent: Save AI conversations across sessions
Streaming vs. Complete responses?
- Streaming: Show AI "typing" in real-time (better UX)
- Complete: Wait for full response (simpler implementation)
Error handling?
- API failures: Fallback to terminal? Show error?
- Rate limits: Queue messages? Show warning?
- Invalid agent: Suggest valid agents? Default to terminal?
Security Considerations
API Key Storage
- Store in environment variables
- Never expose in frontend
- Allow user configuration via settings
Rate Limiting
- Prevent abuse of AI API
- Limit messages per minute/hour
- Show quota to user
Content Filtering
- Don't send sensitive data to AI (passwords, tokens)
- Detect and warn about sensitive content
- Allow user to opt-out of certain context
Local LLM Privacy
- Ensure Ollama runs locally only
- Don't send data externally
- Make this clear to users
Cost Considerations
Anthropic API Pricing (as of 2024)
- Claude 3.5 Sonnet: ~$3 per million input tokens, ~$15 per million output tokens
- Average message: ~500 input tokens, ~1000 output tokens = ~$0.015 per message
- 100 messages/day = ~$1.50/day = ~$45/month
Mitigation:
- Set daily/monthly quota
- Show cost estimate to user
- Offer local LLM as free alternative
- Cache common queries
Next Steps
- Decide on UI approach (A, B, C, or D)
- Choose AI backend (Claude API or local LLM)
- Create POC with minimal implementation
- Test with real workflows
- Iterate based on feedback
- Add advanced features (streaming, context, etc.)
Related Files to Modify
Backend (web/server.js)
- Add
execute-messagesocket handler - Add AI routing logic
- Add Claude API integration
- Add local LLM integration
Frontend (web-react/)
src/types/index.ts- Add AI message typessrc/contexts/ChatContext.tsx- Add agent routingsrc/components/ChatPage.tsx- Add mode toggle UIsrc/components/MessageBubble.tsx- Add AI message rendering
Configuration
.env- AddANTHROPIC_API_KEYpackage.json- Add@anthropic-ai/sdkdependency
Documentation
- Update README with agent usage
- Add examples of @ mention syntax
- Document available agents
- Explain cost implications
Success Metrics
- Adoption: % of users who try AI mode
- Retention: % of users who use it regularly
- Usefulness: User feedback on AI responses
- Performance: Response time, error rate
- Cost: API usage vs. budget
Conclusion
The @ mention approach (Option B) is recommended for MVP because:
- Minimal implementation effort
- No UI changes required
- Natural, familiar pattern
- Easy to test and iterate
Once validated, add mode toggle (Option A) for better discoverability and explicit mode indication.
Eventually, evolve to hybrid approach (Option D) with autocomplete for best UX.