merge: sync with upstream v0.9.0rc branch

This commit is contained in:
DhanushSantosh
2026-01-09 22:10:51 +05:30
23 changed files with 1457 additions and 9 deletions

View File

@@ -5,6 +5,8 @@ import { useCliStatus } from '../hooks/use-cli-status';
import { ClaudeCliStatus } from '../cli-status/claude-cli-status';
import { ClaudeMdSettings } from '../claude/claude-md-settings';
import { ClaudeUsageSection } from '../api-keys/claude-usage-section';
import { SkillsSection } from './claude-settings-tab/skills-section';
import { SubagentsSection } from './claude-settings-tab/subagents-section';
import { Info } from 'lucide-react';
export function ClaudeSettingsTab() {
@@ -43,6 +45,13 @@ export function ClaudeSettingsTab() {
autoLoadClaudeMd={autoLoadClaudeMd}
onAutoLoadClaudeMdChange={setAutoLoadClaudeMd}
/>
{/* Skills Configuration */}
<SkillsSection />
{/* Custom Subagents */}
<SubagentsSection />
{showUsageTracking && <ClaudeUsageSection />}
</div>
);

View File

@@ -0,0 +1,6 @@
/**
* Hooks barrel export for Claude Settings Tab
*/
export { useSkillsSettings } from './use-skills-settings';
export { useSubagents } from './use-subagents';

View File

@@ -0,0 +1,63 @@
/**
* Skills Settings Hook - Manages Skills configuration state
*
* Provides state management for enabling/disabling Skills and
* configuring which sources to load Skills from (user/project).
*/
import { useState } from 'react';
import { useAppStore } from '@/store/app-store';
import { toast } from 'sonner';
import { getElectronAPI } from '@/lib/electron';
export function useSkillsSettings() {
const enabled = useAppStore((state) => state.enableSkills);
const sources = useAppStore((state) => state.skillsSources);
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({ enableSkills: newEnabled });
// Update local store after successful server update
useAppStore.setState({ enableSkills: newEnabled });
toast.success(newEnabled ? 'Skills enabled' : 'Skills disabled');
} catch (error) {
toast.error('Failed to update skills 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({ skillsSources: newSources });
// Update local store after successful server update
useAppStore.setState({ skillsSources: newSources });
toast.success('Skills sources updated');
} catch (error) {
toast.error('Failed to update skills sources');
console.error(error);
} finally {
setIsLoading(false);
}
};
return {
enabled,
sources,
updateEnabled,
updateSources,
isLoading,
};
}

View File

@@ -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,
};
}

View File

@@ -0,0 +1,85 @@
/**
* Subagents Hook - Manages custom subagent definitions
*
* Provides read-only view of custom subagent configurations
* used for specialized task delegation. Supports:
* - Filesystem agents (AGENT.md files in .claude/agents/) - user and project-level (read-only)
*
* Filesystem agents are discovered via the server API and displayed in the UI.
* Agent definitions in settings JSON are used server-side only.
*/
import { useState, useEffect, useCallback } from 'react';
import { useAppStore } from '@/store/app-store';
import type { AgentDefinition } from '@automaker/types';
import { getElectronAPI } from '@/lib/electron';
export type SubagentScope = 'global' | 'project';
export type SubagentType = 'filesystem';
export type FilesystemSource = 'user' | 'project';
export interface SubagentWithScope {
name: string;
definition: AgentDefinition;
scope: SubagentScope;
type: SubagentType;
source: FilesystemSource;
filePath: string;
}
interface FilesystemAgent {
name: string;
definition: AgentDefinition;
source: FilesystemSource;
filePath: string;
}
export function useSubagents() {
const currentProject = useAppStore((state) => state.currentProject);
const [isLoading, setIsLoading] = useState(false);
const [subagentsWithScope, setSubagentsWithScope] = useState<SubagentWithScope[]>([]);
// Fetch filesystem agents
const fetchFilesystemAgents = useCallback(async () => {
setIsLoading(true);
try {
const api = getElectronAPI();
if (!api.settings) {
console.warn('Settings API not available');
return;
}
const data = await api.settings.discoverAgents(currentProject?.path, ['user', 'project']);
if (data.success && data.agents) {
// Transform filesystem agents to SubagentWithScope format
const agents: SubagentWithScope[] = data.agents.map(
({ name, definition, source, filePath }: FilesystemAgent) => ({
name,
definition,
scope: source === 'user' ? 'global' : 'project',
type: 'filesystem' as const,
source,
filePath,
})
);
setSubagentsWithScope(agents);
}
} catch (error) {
console.error('Failed to fetch filesystem agents:', error);
} finally {
setIsLoading(false);
}
}, [currentProject?.path]);
// Fetch filesystem agents on mount and when project changes
useEffect(() => {
fetchFilesystemAgents();
}, [fetchFilesystemAgents]);
return {
subagentsWithScope,
isLoading,
hasProject: !!currentProject,
refreshFilesystemAgents: fetchFilesystemAgents,
};
}

View File

@@ -0,0 +1,7 @@
/**
* Claude Settings Tab components barrel export
*/
export { SkillsSection } from './skills-section';
export { SubagentsSection } from './subagents-section';
export { SubagentCard } from './subagent-card';

View File

@@ -0,0 +1,169 @@
/**
* Skills Section - UI for managing Skills configuration
*
* Allows users to enable/disable Skills and select which directories
* to load Skills from (user ~/.claude/skills/ or project .claude/skills/).
*/
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils';
import { Zap, Globe, FolderOpen, ExternalLink, Sparkles } from 'lucide-react';
import { useSkillsSettings } from './hooks/use-skills-settings';
export function SkillsSection() {
const { enabled, sources, updateEnabled, updateSources, isLoading } = useSkillsSettings();
const toggleSource = (source: 'user' | 'project') => {
if (sources.includes(source)) {
updateSources(sources.filter((s: 'user' | 'project') => s !== source));
} else {
updateSources([...sources, source]);
}
};
return (
<div
className={cn(
'rounded-2xl overflow-hidden',
'border border-border/50',
'bg-linear-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
'shadow-sm shadow-black/5'
)}
>
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-border/30">
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-xl bg-amber-500/20 flex items-center justify-center">
<Zap className="w-5 h-5 text-amber-500" />
</div>
<div>
<h3 className="font-semibold text-base flex items-center gap-2">
Skills
{enabled && (
<span className="text-xs font-normal px-2 py-0.5 rounded-full bg-amber-500/20 text-amber-500">
{sources.length} source{sources.length !== 1 ? 's' : ''} active
</span>
)}
</h3>
<p className="text-sm text-muted-foreground mt-0.5">
Filesystem-based capabilities Claude invokes autonomously
</p>
</div>
</div>
<Switch
id="enable-skills"
checked={enabled}
onCheckedChange={updateEnabled}
disabled={isLoading}
/>
</div>
{/* Content */}
<div className="p-6 space-y-4">
{/* Sources Selection */}
{enabled && (
<div className="space-y-3">
<Label className="text-xs uppercase tracking-wide text-muted-foreground">
Load Skills from
</Label>
<div className="grid gap-2">
{/* User Skills Option */}
<label
htmlFor="source-user"
className={cn(
'flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-all duration-200',
sources.includes('user')
? 'border-amber-500/50 bg-amber-500/10'
: 'border-border/50 bg-accent/20 hover:bg-accent/30'
)}
>
<Checkbox
id="source-user"
checked={sources.includes('user')}
onCheckedChange={() => toggleSource('user')}
disabled={isLoading}
className="data-[state=checked]:bg-amber-500 data-[state=checked]:border-amber-500"
/>
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center shrink-0">
<Globe className="w-4 h-4 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium">User Skills</span>
<span className="block text-xs text-muted-foreground mt-0.5 truncate">
~/.claude/skills/ Available across all projects
</span>
</div>
</label>
{/* Project Skills Option */}
<label
htmlFor="source-project"
className={cn(
'flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-all duration-200',
sources.includes('project')
? 'border-amber-500/50 bg-amber-500/10'
: 'border-border/50 bg-accent/20 hover:bg-accent/30'
)}
>
<Checkbox
id="source-project"
checked={sources.includes('project')}
onCheckedChange={() => toggleSource('project')}
disabled={isLoading}
className="data-[state=checked]:bg-amber-500 data-[state=checked]:border-amber-500"
/>
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center shrink-0">
<FolderOpen className="w-4 h-4 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium">Project Skills</span>
<span className="block text-xs text-muted-foreground mt-0.5 truncate">
.claude/skills/ Version-controlled with project
</span>
</div>
</label>
</div>
</div>
)}
{/* Help Text */}
{enabled && (
<div className="rounded-xl border border-border/30 bg-muted/30 p-4 space-y-3">
<div className="flex items-start gap-3">
<div className="w-6 h-6 rounded-md bg-brand-500/20 flex items-center justify-center shrink-0 mt-0.5">
<Sparkles className="w-3.5 h-3.5 text-brand-500" />
</div>
<div className="text-xs text-muted-foreground space-y-1">
<p className="font-medium text-foreground/80">Auto-Discovery</p>
<p>
Skills are automatically discovered when agents start. Define skills as{' '}
<code className="text-xs bg-muted px-1 rounded">SKILL.md</code> files.
</p>
</div>
</div>
<a
href="https://code.claude.com/docs/en/skills"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-xs text-brand-500 hover:text-brand-400 transition-colors"
>
<ExternalLink className="w-3.5 h-3.5" />
View Skills documentation
</a>
</div>
)}
{/* Disabled State Empty Message */}
{!enabled && (
<div className="text-center py-6 text-muted-foreground">
<Zap className="w-10 h-10 mx-auto mb-3 opacity-30" />
<p className="text-sm">Skills are disabled</p>
<p className="text-xs mt-1">Enable to load filesystem-based capabilities</p>
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,138 @@
/**
* Subagent Card - Display card for a single subagent definition
*
* Shows the subagent's name, description, model, tool count, scope, and type.
* Read-only view - agents are managed by editing .md files directly.
*/
import { useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Markdown } from '@/components/ui/markdown';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
import {
Globe,
FolderOpen,
ChevronDown,
ChevronRight,
Bot,
Cpu,
Wrench,
FileCode,
} from 'lucide-react';
import type { SubagentWithScope } from './hooks/use-subagents';
interface SubagentCardProps {
agent: SubagentWithScope;
}
export function SubagentCard({ agent }: SubagentCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const { name, definition, scope, filePath } = agent;
const toolCount = definition.tools?.length ?? 'all';
const modelDisplay =
definition.model === 'inherit' || !definition.model
? 'Inherit'
: definition.model.charAt(0).toUpperCase() + definition.model.slice(1);
// Scope icon and label
const ScopeIcon = scope === 'global' ? Globe : FolderOpen;
const scopeLabel = scope === 'global' ? 'User' : 'Project';
// Model color based on type
const getModelColor = () => {
const model = definition.model?.toLowerCase();
if (model === 'opus') return 'text-violet-500 bg-violet-500/10 border-violet-500/30';
if (model === 'sonnet') return 'text-blue-500 bg-blue-500/10 border-blue-500/30';
if (model === 'haiku') return 'text-emerald-500 bg-emerald-500/10 border-emerald-500/30';
return 'text-muted-foreground bg-muted/50 border-border/50';
};
return (
<Collapsible open={isExpanded} onOpenChange={setIsExpanded}>
<div
className={cn(
'rounded-xl border transition-all duration-200',
'border-border/50 bg-accent/20',
'hover:bg-accent/30 hover:border-border/70'
)}
>
{/* Main Card Content */}
<div className="flex items-start gap-3 p-4">
{/* Agent Icon */}
<div className="w-9 h-9 rounded-lg bg-violet-500/15 flex items-center justify-center shrink-0 mt-0.5">
<Bot className="w-4 h-4 text-violet-500" />
</div>
{/* Content */}
<div className="flex-1 min-w-0">
{/* Header Row */}
<div className="flex items-center gap-2 flex-wrap">
<h4 className="font-medium text-sm">{name}</h4>
<Badge
variant="outline"
size="sm"
className={cn('flex items-center gap-1', getModelColor())}
>
<Cpu className="h-3 w-3" />
{modelDisplay}
</Badge>
<Badge variant="muted" size="sm" className="flex items-center gap-1">
<Wrench className="h-3 w-3" />
{toolCount === 'all' ? 'All' : toolCount} tools
</Badge>
<Badge variant="muted" size="sm" className="flex items-center gap-1">
<ScopeIcon className="h-3 w-3" />
{scopeLabel}
</Badge>
</div>
{/* Description */}
<p className="text-sm text-muted-foreground mt-1.5 line-clamp-2">
{definition.description}
</p>
{/* File Path */}
{filePath && (
<div className="flex items-center gap-1.5 mt-2 text-xs text-muted-foreground/60">
<FileCode className="h-3 w-3" />
<span className="font-mono truncate">{filePath}</span>
</div>
)}
</div>
{/* Expand Button */}
<CollapsibleTrigger asChild>
<button
className={cn(
'p-1.5 rounded-md transition-colors shrink-0',
'hover:bg-muted/50 text-muted-foreground hover:text-foreground',
'cursor-pointer'
)}
title={isExpanded ? 'Hide prompt' : 'View prompt'}
>
{isExpanded ? (
<ChevronDown className="w-4 h-4" />
) : (
<ChevronRight className="w-4 h-4" />
)}
</button>
</CollapsibleTrigger>
</div>
{/* Expandable Prompt Section */}
<CollapsibleContent>
<div className="px-4 pb-4 pt-0">
<div className="ml-12 rounded-lg border border-border/30 bg-muted/30 p-4 overflow-auto max-h-64">
<div className="text-xs font-medium text-muted-foreground mb-2 uppercase tracking-wide">
System Prompt
</div>
<Markdown className="text-xs prose-sm">{definition.prompt}</Markdown>
</div>
</div>
</CollapsibleContent>
</div>
</Collapsible>
);
}

View File

@@ -0,0 +1,257 @@
/**
* 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/
*/
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,
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: 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 (
<div
className={cn(
'rounded-2xl overflow-hidden',
'border border-border/50',
'bg-linear-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
'shadow-sm shadow-black/5'
)}
>
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-border/30">
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-xl bg-violet-500/20 flex items-center justify-center">
<Bot className="w-5 h-5 text-violet-500" />
</div>
<div>
<h3 className="font-semibold text-base flex items-center gap-2">
Custom Subagents
{enabled && subagentsWithScope.length > 0 && (
<span className="text-xs font-normal px-2 py-0.5 rounded-full bg-violet-500/20 text-violet-500">
{subagentsWithScope.length} agent{subagentsWithScope.length !== 1 ? 's' : ''}
</span>
)}
</h3>
<p className="text-sm text-muted-foreground mt-0.5">
Specialized agents Claude delegates to automatically
</p>
</div>
</div>
<Switch
id="enable-subagents"
checked={enabled}
onCheckedChange={updateEnabled}
disabled={isLoading}
/>
</div>
{/* Content */}
<div className="p-6 space-y-4">
{/* Sources Selection */}
{enabled && (
<div className="space-y-3">
<Label className="text-xs uppercase tracking-wide text-muted-foreground">
Load Subagents from
</Label>
<div className="grid gap-2">
{/* User Subagents Option */}
<label
htmlFor="subagent-source-user"
className={cn(
'flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-all duration-200',
sources.includes('user')
? 'border-violet-500/50 bg-violet-500/10'
: 'border-border/50 bg-accent/20 hover:bg-accent/30'
)}
>
<Checkbox
id="subagent-source-user"
checked={sources.includes('user')}
onCheckedChange={() => toggleSource('user')}
disabled={isLoading}
className="data-[state=checked]:bg-violet-500 data-[state=checked]:border-violet-500"
/>
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center shrink-0">
<Globe className="w-4 h-4 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium">User Subagents</span>
<span className="block text-xs text-muted-foreground mt-0.5 truncate">
~/.claude/agents/ Available across all projects
</span>
</div>
</label>
{/* Project Subagents Option */}
<label
htmlFor="subagent-source-project"
className={cn(
'flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-all duration-200',
sources.includes('project')
? 'border-violet-500/50 bg-violet-500/10'
: 'border-border/50 bg-accent/20 hover:bg-accent/30'
)}
>
<Checkbox
id="subagent-source-project"
checked={sources.includes('project')}
onCheckedChange={() => toggleSource('project')}
disabled={isLoading}
className="data-[state=checked]:bg-violet-500 data-[state=checked]:border-violet-500"
/>
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center shrink-0">
<FolderOpen className="w-4 h-4 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium">Project Subagents</span>
<span className="block text-xs text-muted-foreground mt-0.5 truncate">
.claude/agents/ Version-controlled with project
</span>
</div>
</label>
</div>
</div>
)}
{/* Agents List */}
{enabled && (
<>
{/* Refresh Button */}
<div className="flex items-center justify-between">
<Label className="text-xs uppercase tracking-wide text-muted-foreground">
Discovered Agents
</Label>
<Button
variant="ghost"
size="sm"
onClick={handleRefresh}
disabled={isLoading}
title="Refresh agents from disk"
className="gap-1.5 h-7 px-2 text-xs"
>
{isLoadingAgents ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<RefreshCw className="h-3.5 w-3.5" />
)}
Refresh
</Button>
</div>
{subagentsWithScope.length === 0 ? (
<div className="text-center py-6 text-muted-foreground border border-dashed border-border/50 rounded-xl">
<Users className="w-10 h-10 mx-auto mb-2 opacity-30" />
<p className="text-sm font-medium">No agents found</p>
<p className="text-xs mt-1 max-w-sm mx-auto">
Create <code className="text-xs bg-muted px-1 rounded">.md</code> files in{' '}
{sources.includes('user') && (
<code className="text-xs bg-muted px-1 rounded">~/.claude/agents/</code>
)}
{sources.includes('user') && sources.includes('project') && ' or '}
{sources.includes('project') && (
<code className="text-xs bg-muted px-1 rounded">.claude/agents/</code>
)}
</p>
</div>
) : (
<div className="space-y-2">
{subagentsWithScope.map((agent) => (
<SubagentCard
key={`${agent.type}-${agent.source || agent.scope}-${agent.name}`}
agent={agent}
/>
))}
</div>
)}
</>
)}
{/* Help Text */}
{enabled && (
<div className="rounded-xl border border-border/30 bg-muted/30 p-4 space-y-3">
<div className="flex items-start gap-3">
<div className="w-6 h-6 rounded-md bg-brand-500/20 flex items-center justify-center shrink-0 mt-0.5">
<Sparkles className="w-3.5 h-3.5 text-brand-500" />
</div>
<div className="text-xs text-muted-foreground space-y-1">
<p className="font-medium text-foreground/80">Auto-Discovery</p>
<p>
Subagents are automatically discovered when agents start. Define agents as{' '}
<code className="text-xs bg-muted px-1 rounded">AGENT.md</code> files or{' '}
<code className="text-xs bg-muted px-1 rounded">agent-name.md</code> files.
</p>
</div>
</div>
<a
href="https://code.claude.com/docs/en/agents"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-xs text-brand-500 hover:text-brand-400 transition-colors"
>
<ExternalLink className="w-3.5 h-3.5" />
View Agents documentation
</a>
</div>
)}
{/* Disabled State Empty Message */}
{!enabled && (
<div className="text-center py-6 text-muted-foreground">
<Bot className="w-10 h-10 mx-auto mb-3 opacity-30" />
<p className="text-sm">Subagents are disabled</p>
<p className="text-xs mt-1">Enable to load custom agent definitions</p>
</div>
)}
</div>
</div>
);
}

View File

@@ -728,6 +728,83 @@ export interface ElectronAPI {
codex?: {
getUsage: () => Promise<CodexUsageResponse>;
};
settings?: {
getStatus: () => Promise<{
success: boolean;
hasGlobalSettings: boolean;
hasCredentials: boolean;
dataDir: string;
needsMigration: boolean;
}>;
getGlobal: () => Promise<{
success: boolean;
settings?: Record<string, unknown>;
error?: string;
}>;
updateGlobal: (updates: Record<string, unknown>) => Promise<{
success: boolean;
settings?: Record<string, unknown>;
error?: string;
}>;
getCredentials: () => Promise<{
success: boolean;
credentials?: {
anthropic: { configured: boolean; masked: string };
google: { configured: boolean; masked: string };
openai: { configured: boolean; masked: string };
};
error?: string;
}>;
updateCredentials: (updates: {
apiKeys?: { anthropic?: string; google?: string; openai?: string };
}) => Promise<{
success: boolean;
credentials?: {
anthropic: { configured: boolean; masked: string };
google: { configured: boolean; masked: string };
openai: { configured: boolean; masked: string };
};
error?: string;
}>;
getProject: (projectPath: string) => Promise<{
success: boolean;
settings?: Record<string, unknown>;
error?: string;
}>;
updateProject: (
projectPath: string,
updates: Record<string, unknown>
) => Promise<{
success: boolean;
settings?: Record<string, unknown>;
error?: string;
}>;
migrate: (data: Record<string, string>) => Promise<{
success: boolean;
migratedGlobalSettings: boolean;
migratedCredentials: boolean;
migratedProjectCount: number;
errors: string[];
}>;
discoverAgents: (
projectPath?: string,
sources?: Array<'user' | 'project'>
) => Promise<{
success: boolean;
agents?: Array<{
name: string;
definition: {
description: string;
prompt: string;
tools?: string[];
model?: 'sonnet' | 'opus' | 'haiku' | 'inherit';
};
source: 'user' | 'project';
filePath: string;
}>;
error?: string;
}>;
};
}
// Note: Window interface is declared in @/types/electron.d.ts

View File

@@ -23,8 +23,6 @@ import type {
SpecRegenerationEvent,
SuggestionType,
GitHubAPI,
GitHubIssue,
GitHubPR,
IssueValidationInput,
IssueValidationEvent,
IdeationAPI,
@@ -1891,6 +1889,26 @@ export class HttpApiClient implements ElectronAPI {
migratedProjectCount: number;
errors: string[];
}> => this.post('/api/settings/migrate', { data }),
// Filesystem agents discovery (read-only)
discoverAgents: (
projectPath?: string,
sources?: Array<'user' | 'project'>
): Promise<{
success: boolean;
agents?: Array<{
name: string;
definition: {
description: string;
prompt: string;
tools?: string[];
model?: 'sonnet' | 'opus' | 'haiku' | 'inherit';
};
source: 'user' | 'project';
filePath: string;
}>;
error?: string;
}> => this.post('/api/settings/agents/discover', { projectPath, sources }),
};
// Sessions API

View File

@@ -5,7 +5,6 @@
import { createLogger } from '@automaker/utils/logger';
import { getHttpApiClient } from './http-api-client';
import { getElectronAPI } from './electron';
import { useAppStore } from '@/store/app-store';
const logger = createLogger('WorkspaceConfig');
@@ -33,9 +32,17 @@ function joinPath(...parts: string[]): string {
*/
async function getDefaultDocumentsPath(): Promise<string | null> {
try {
const api = getElectronAPI();
const documentsPath = await api.getPath('documents');
return joinPath(documentsPath, 'Automaker');
// In Electron mode, use the native getPath API directly from the preload script
// This returns the actual system Documents folder (e.g., C:\Users\<user>\Documents on Windows)
// Note: The HTTP client's getPath returns incorrect Unix-style paths for 'documents'
if (typeof window !== 'undefined' && (window as any).electronAPI?.getPath) {
const documentsPath = await (window as any).electronAPI.getPath('documents');
return joinPath(documentsPath, 'Automaker');
}
// In web mode (no Electron), we can't access the user's Documents folder
// Return null to let the caller use other fallback mechanisms (like server's DATA_DIR)
return null;
} catch (error) {
logger.error('Failed to get documents path:', error);
return null;
@@ -76,6 +83,7 @@ export async function getDefaultWorkspaceDirectory(): Promise<string | null> {
// Try to get Documents/Automaker
const documentsPath = await getDefaultDocumentsPath();
logger.info('Default documentsPath resolved to:', documentsPath);
if (documentsPath) {
return documentsPath;
}

View File

@@ -574,6 +574,14 @@ export interface AppState {
// MCP Servers
mcpServers: MCPServerConfig[]; // List of configured MCP servers for agent use
// Skills Configuration
enableSkills: boolean; // Enable Skills functionality (loads from .claude/skills/ directories)
skillsSources: Array<'user' | 'project'>; // Which directories to load Skills from
// Subagents Configuration
enableSubagents: boolean; // Enable Custom Subagents functionality (loads from .claude/agents/ directories)
subagentsSources: Array<'user' | 'project'>; // Which directories to load Subagents from
// Prompt Customization
promptCustomization: PromptCustomization; // Custom prompts for Auto Mode, Agent, Backlog Plan, Enhancement
@@ -1170,6 +1178,10 @@ const initialState: AppState = {
autoLoadClaudeMd: false, // Default to disabled (user must opt-in)
skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog)
mcpServers: [], // No MCP servers configured by default
enableSkills: true, // Skills enabled by default
skillsSources: ['user', 'project'] as Array<'user' | 'project'>, // Load from both sources by default
enableSubagents: true, // Subagents enabled by default
subagentsSources: ['user', 'project'] as Array<'user' | 'project'>, // Load from both sources by default
promptCustomization: {}, // Empty by default - all prompts use built-in defaults
aiProfiles: DEFAULT_AI_PROFILES,
projectAnalysis: null,