diff --git a/apps/server/src/lib/settings-helpers.ts b/apps/server/src/lib/settings-helpers.ts index 07564085..0cab0121 100644 --- a/apps/server/src/lib/settings-helpers.ts +++ b/apps/server/src/lib/settings-helpers.ts @@ -293,6 +293,29 @@ export async function getSkillsConfiguration(settingsService: SettingsService): }; } +/** + * Get Subagents configuration from settings. + * Returns configuration for enabling subagents and which sources to load from. + * + * @param settingsService - Settings service instance + * @returns Subagents configuration with enabled state, sources, and tool inclusion flag + */ +export async function getSubagentsConfiguration(settingsService: SettingsService): Promise<{ + enabled: boolean; + sources: Array<'user' | 'project'>; + shouldIncludeInTools: boolean; +}> { + const settings = await settingsService.getGlobalSettings(); + const enabled = settings.enableSubagents ?? true; // Default enabled + const sources = settings.subagentsSources ?? ['user', 'project']; // Default both sources + + return { + enabled, + sources, + shouldIncludeInTools: enabled && sources.length > 0, + }; +} + /** * Get custom subagents from settings, merging global and project-level definitions. * Project-level subagents take precedence over global ones with the same name. diff --git a/apps/server/src/providers/claude-provider.ts b/apps/server/src/providers/claude-provider.ts index 5d033a3b..90defa72 100644 --- a/apps/server/src/providers/claude-provider.ts +++ b/apps/server/src/providers/claude-provider.ts @@ -72,17 +72,10 @@ export class ClaudeProvider extends BaseProvider { // Build Claude SDK options // AUTONOMOUS MODE: Always bypass permissions for fully autonomous operation const hasMcpServers = options.mcpServers && Object.keys(options.mcpServers).length > 0; - const defaultTools = [ - 'Read', - 'Write', - 'Edit', - 'Glob', - 'Grep', - 'Bash', - 'WebSearch', - 'WebFetch', - 'Skill', - ]; + // Base tools available to all agents + // Note: 'Skill' and 'Task' tools are added dynamically by agent-service.ts + // based on whether skills/subagents are enabled in settings + const defaultTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch']; // AUTONOMOUS MODE: Always bypass permissions and allow unrestricted tools // Only restrict tools when no MCP servers are configured diff --git a/apps/server/src/services/agent-service.ts b/apps/server/src/services/agent-service.ts index 1d543efc..bbc060d1 100644 --- a/apps/server/src/services/agent-service.ts +++ b/apps/server/src/services/agent-service.ts @@ -25,6 +25,7 @@ import { getMCPServersFromSettings, getPromptCustomization, getSkillsConfiguration, + getSubagentsConfiguration, getCustomSubagents, } from '../lib/settings-helpers.js'; @@ -248,10 +249,16 @@ export class AgentService { ? await getSkillsConfiguration(this.settingsService) : { enabled: false, sources: [] as Array<'user' | 'project'>, shouldIncludeInTools: false }; - // Get custom subagents from settings (merge global + project-level) - const customSubagents = this.settingsService - ? await getCustomSubagents(this.settingsService, effectiveWorkDir) - : undefined; + // Get Subagents configuration from settings + const subagentsConfig = this.settingsService + ? await getSubagentsConfiguration(this.settingsService) + : { enabled: false, sources: [] as Array<'user' | 'project'>, shouldIncludeInTools: false }; + + // Get custom subagents from settings (merge global + project-level) only if enabled + const customSubagents = + this.settingsService && subagentsConfig.enabled + ? await getCustomSubagents(this.settingsService, effectiveWorkDir) + : undefined; // Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.) const contextResult = await loadContextFiles({ @@ -297,18 +304,34 @@ export class AgentService { const settingSources = [...new Set([...sdkSettingSources, ...skillSettingSources])]; // Enhance allowedTools with Skills and Subagents tools + // These tools are not in the provider's default set - they're added dynamically based on settings + const needsSkillTool = skillsConfig.shouldIncludeInTools; + const needsTaskTool = + subagentsConfig.shouldIncludeInTools && + customSubagents && + Object.keys(customSubagents).length > 0; + + // Base tools that match the provider's default set + const baseTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch']; + if (allowedTools) { allowedTools = [...allowedTools]; // Create a copy to avoid mutating SDK options // Add Skill tool if skills are enabled - if (skillsConfig.shouldIncludeInTools && !allowedTools.includes('Skill')) { + if (needsSkillTool && !allowedTools.includes('Skill')) { allowedTools.push('Skill'); } // Add Task tool if custom subagents are configured - if ( - customSubagents && - Object.keys(customSubagents).length > 0 && - !allowedTools.includes('Task') - ) { + if (needsTaskTool && !allowedTools.includes('Task')) { + allowedTools.push('Task'); + } + } else if (needsSkillTool || needsTaskTool) { + // If no allowedTools specified but we need to add Skill/Task tools, + // build the full list including base tools + allowedTools = [...baseTools]; + if (needsSkillTool) { + allowedTools.push('Skill'); + } + if (needsTaskTool) { allowedTools.push('Task'); } } diff --git a/apps/server/tests/unit/providers/claude-provider.test.ts b/apps/server/tests/unit/providers/claude-provider.test.ts index 3a91652a..40d3b5b7 100644 --- a/apps/server/tests/unit/providers/claude-provider.test.ts +++ b/apps/server/tests/unit/providers/claude-provider.test.ts @@ -96,17 +96,9 @@ describe('claude-provider.ts', () => { expect(sdk.query).toHaveBeenCalledWith({ prompt: 'Test', options: expect.objectContaining({ - allowedTools: [ - 'Read', - 'Write', - 'Edit', - 'Glob', - 'Grep', - 'Bash', - 'WebSearch', - 'WebFetch', - 'Skill', - ], + // Note: 'Skill' and 'Task' tools are added dynamically by agent-service.ts + // based on settings, not included in base default tools + allowedTools: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'], }), }); }); diff --git a/apps/ui/src/components/views/settings-view/providers/claude-settings-tab/hooks/use-subagents-settings.ts b/apps/ui/src/components/views/settings-view/providers/claude-settings-tab/hooks/use-subagents-settings.ts new file mode 100644 index 00000000..ccf7664a --- /dev/null +++ b/apps/ui/src/components/views/settings-view/providers/claude-settings-tab/hooks/use-subagents-settings.ts @@ -0,0 +1,63 @@ +/** + * Subagents Settings Hook - Manages Subagents configuration state + * + * Provides state management for enabling/disabling Subagents and + * configuring which sources to load Subagents from (user/project). + */ + +import { useState } from 'react'; +import { useAppStore } from '@/store/app-store'; +import { toast } from 'sonner'; +import { getElectronAPI } from '@/lib/electron'; + +export function useSubagentsSettings() { + const enabled = useAppStore((state) => state.enableSubagents); + const sources = useAppStore((state) => state.subagentsSources); + const [isLoading, setIsLoading] = useState(false); + + const updateEnabled = async (newEnabled: boolean) => { + setIsLoading(true); + try { + const api = getElectronAPI(); + if (!api.settings) { + throw new Error('Settings API not available'); + } + await api.settings.updateGlobal({ enableSubagents: newEnabled }); + // Update local store after successful server update + useAppStore.setState({ enableSubagents: newEnabled }); + toast.success(newEnabled ? 'Subagents enabled' : 'Subagents disabled'); + } catch (error) { + toast.error('Failed to update subagents settings'); + console.error(error); + } finally { + setIsLoading(false); + } + }; + + const updateSources = async (newSources: Array<'user' | 'project'>) => { + setIsLoading(true); + try { + const api = getElectronAPI(); + if (!api.settings) { + throw new Error('Settings API not available'); + } + await api.settings.updateGlobal({ subagentsSources: newSources }); + // Update local store after successful server update + useAppStore.setState({ subagentsSources: newSources }); + toast.success('Subagents sources updated'); + } catch (error) { + toast.error('Failed to update subagents sources'); + console.error(error); + } finally { + setIsLoading(false); + } + }; + + return { + enabled, + sources, + updateEnabled, + updateSources, + isLoading, + }; +} diff --git a/apps/ui/src/components/views/settings-view/providers/claude-settings-tab/subagents-section.tsx b/apps/ui/src/components/views/settings-view/providers/claude-settings-tab/subagents-section.tsx index c6bcd979..08800331 100644 --- a/apps/ui/src/components/views/settings-view/providers/claude-settings-tab/subagents-section.tsx +++ b/apps/ui/src/components/views/settings-view/providers/claude-settings-tab/subagents-section.tsx @@ -1,26 +1,62 @@ /** - * Subagents Section - UI for viewing filesystem-based agents + * Subagents Section - UI for managing Subagents configuration + * + * Allows users to enable/disable Subagents and select which directories + * to load Subagents from (user ~/.claude/agents/ or project .claude/agents/). * * Displays agents discovered from: * - User-level: ~/.claude/agents/ * - Project-level: .claude/agents/ - * - * Read-only view - agents are managed by editing .md files directly. */ import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; +import { Checkbox } from '@/components/ui/checkbox'; import { cn } from '@/lib/utils'; -import { Bot, RefreshCw, Loader2, Users, ExternalLink } from 'lucide-react'; +import { + Bot, + RefreshCw, + Loader2, + Users, + ExternalLink, + Globe, + FolderOpen, + Sparkles, +} from 'lucide-react'; import { useSubagents } from './hooks/use-subagents'; +import { useSubagentsSettings } from './hooks/use-subagents-settings'; import { SubagentCard } from './subagent-card'; export function SubagentsSection() { - const { subagentsWithScope, isLoading, hasProject, refreshFilesystemAgents } = useSubagents(); + const { + subagentsWithScope, + isLoading: isLoadingAgents, + hasProject, + refreshFilesystemAgents, + } = useSubagents(); + const { + enabled, + sources, + updateEnabled, + updateSources, + isLoading: isLoadingSettings, + } = useSubagentsSettings(); + + const isLoading = isLoadingAgents || isLoadingSettings; const handleRefresh = async () => { await refreshFilesystemAgents(); }; + const toggleSource = (source: 'user' | 'project') => { + if (sources.includes(source)) { + updateSources(sources.filter((s: 'user' | 'project') => s !== source)); + } else { + updateSources([...sources, source]); + } + }; + return (
No agents found
-
- Create .md files in{' '}
- ~/.claude/agents/
- {hasProject && (
- <>
- {' or '}
- .claude/agents/
- >
- )}
-
No agents found
+
+ Create .md files in{' '}
+ {sources.includes('user') && (
+ ~/.claude/agents/
+ )}
+ {sources.includes('user') && sources.includes('project') && ' or '}
+ {sources.includes('project') && (
+ .claude/agents/
+ )}
+
Auto-Discovery
+
+ Subagents are automatically discovered when agents start. Define agents as{' '}
+ AGENT.md files or{' '}
+ agent-name.md files.
+
Subagents are disabled
+Enable to load custom agent definitions