mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
refactor: enhance skills and subagents settings management
- Updated useSkillsSettings and useSubagents hooks to improve state management and error handling. - Added new settings API methods for skills configuration and agent discovery. - Refactored app-store to include enableSkills and skillsSources state management. - Enhanced settings migration to sync skills configuration with the server. These changes streamline the management of skills and subagents, ensuring better integration and user experience.
This commit is contained in:
@@ -11,17 +11,20 @@ import { toast } from 'sonner';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
|
||||
export function useSkillsSettings() {
|
||||
const { settings } = useAppStore();
|
||||
const enabled = useAppStore((state) => state.enableSkills);
|
||||
const sources = useAppStore((state) => state.skillsSources);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const enabled = settings?.enableSkills ?? true;
|
||||
const sources = settings?.skillsSources ?? ['user', 'project'];
|
||||
|
||||
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');
|
||||
@@ -35,7 +38,12 @@ export function useSkillsSettings() {
|
||||
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');
|
||||
|
||||
@@ -3,27 +3,28 @@
|
||||
*
|
||||
* Provides read-only view of custom subagent configurations
|
||||
* used for specialized task delegation. Supports:
|
||||
* - Programmatic agents (stored in settings JSON) - global and project-level
|
||||
* - 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 } from 'react';
|
||||
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 = 'programmatic' | 'filesystem';
|
||||
export type SubagentType = 'filesystem';
|
||||
export type FilesystemSource = 'user' | 'project';
|
||||
|
||||
export interface SubagentWithScope {
|
||||
name: string;
|
||||
definition: AgentDefinition;
|
||||
scope: SubagentScope; // For programmatic agents
|
||||
scope: SubagentScope;
|
||||
type: SubagentType;
|
||||
// For filesystem agents:
|
||||
source?: FilesystemSource;
|
||||
filePath?: string;
|
||||
source: FilesystemSource;
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
interface FilesystemAgent {
|
||||
@@ -34,71 +35,46 @@ interface FilesystemAgent {
|
||||
}
|
||||
|
||||
export function useSubagents() {
|
||||
const { settings, currentProject, projectSettings } = useAppStore();
|
||||
const currentProject = useAppStore((state) => state.currentProject);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [subagentsWithScope, setSubagentsWithScope] = useState<SubagentWithScope[]>([]);
|
||||
const [filesystemAgents, setFilesystemAgents] = useState<FilesystemAgent[]>([]);
|
||||
|
||||
// Fetch filesystem agents
|
||||
const fetchFilesystemAgents = async () => {
|
||||
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) {
|
||||
setFilesystemAgents(data.agents || []);
|
||||
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();
|
||||
}, [currentProject?.path]);
|
||||
|
||||
// Merge programmatic and filesystem agents
|
||||
useEffect(() => {
|
||||
const globalSubagents = settings?.customSubagents || {};
|
||||
const projectSubagents = projectSettings?.customSubagents || {};
|
||||
|
||||
const merged: SubagentWithScope[] = [];
|
||||
|
||||
// Add programmatic global agents
|
||||
Object.entries(globalSubagents).forEach(([name, definition]) => {
|
||||
merged.push({ name, definition, scope: 'global', type: 'programmatic' });
|
||||
});
|
||||
|
||||
// Add programmatic project agents (override globals with same name)
|
||||
Object.entries(projectSubagents).forEach(([name, definition]) => {
|
||||
const globalIndex = merged.findIndex((s) => s.name === name && s.scope === 'global');
|
||||
if (globalIndex !== -1) {
|
||||
merged.splice(globalIndex, 1);
|
||||
}
|
||||
merged.push({ name, definition, scope: 'project', type: 'programmatic' });
|
||||
});
|
||||
|
||||
// Add filesystem agents
|
||||
filesystemAgents.forEach(({ name, definition, source, filePath }) => {
|
||||
// Remove any programmatic agents with the same name (filesystem takes precedence)
|
||||
const programmaticIndex = merged.findIndex((s) => s.name === name);
|
||||
if (programmaticIndex !== -1) {
|
||||
merged.splice(programmaticIndex, 1);
|
||||
}
|
||||
|
||||
merged.push({
|
||||
name,
|
||||
definition,
|
||||
scope: source === 'user' ? 'global' : 'project',
|
||||
type: 'filesystem',
|
||||
source,
|
||||
filePath,
|
||||
});
|
||||
});
|
||||
|
||||
setSubagentsWithScope(merged);
|
||||
}, [settings?.customSubagents, projectSettings?.customSubagents, filesystemAgents]);
|
||||
}, [fetchFilesystemAgents]);
|
||||
|
||||
return {
|
||||
subagentsWithScope,
|
||||
|
||||
@@ -234,6 +234,8 @@ export async function syncSettingsToServer(): Promise<boolean> {
|
||||
keyboardShortcuts: state.keyboardShortcuts,
|
||||
aiProfiles: state.aiProfiles,
|
||||
mcpServers: state.mcpServers,
|
||||
enableSkills: state.enableSkills,
|
||||
skillsSources: state.skillsSources,
|
||||
promptCustomization: state.promptCustomization,
|
||||
projects: state.projects,
|
||||
trashedProjects: state.trashedProjects,
|
||||
|
||||
@@ -758,6 +758,83 @@ export interface ElectronAPI {
|
||||
}>;
|
||||
};
|
||||
ideation?: IdeationAPI;
|
||||
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
|
||||
|
||||
@@ -512,6 +512,10 @@ 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
|
||||
|
||||
// Prompt Customization
|
||||
promptCustomization: PromptCustomization; // Custom prompts for Auto Mode, Agent, Backlog Plan, Enhancement
|
||||
|
||||
@@ -1022,6 +1026,8 @@ const initialState: AppState = {
|
||||
enableSandboxMode: false, // Default to disabled (can be enabled for additional security)
|
||||
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
|
||||
promptCustomization: {}, // Empty by default - all prompts use built-in defaults
|
||||
aiProfiles: DEFAULT_AI_PROFILES,
|
||||
projectAnalysis: null,
|
||||
|
||||
Reference in New Issue
Block a user