mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +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';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
|
||||||
export function useSkillsSettings() {
|
export function useSkillsSettings() {
|
||||||
const { settings } = useAppStore();
|
const enabled = useAppStore((state) => state.enableSkills);
|
||||||
|
const sources = useAppStore((state) => state.skillsSources);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const enabled = settings?.enableSkills ?? true;
|
|
||||||
const sources = settings?.skillsSources ?? ['user', 'project'];
|
|
||||||
|
|
||||||
const updateEnabled = async (newEnabled: boolean) => {
|
const updateEnabled = async (newEnabled: boolean) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
throw new Error('Settings API not available');
|
||||||
|
}
|
||||||
await api.settings.updateGlobal({ enableSkills: newEnabled });
|
await api.settings.updateGlobal({ enableSkills: newEnabled });
|
||||||
|
// Update local store after successful server update
|
||||||
|
useAppStore.setState({ enableSkills: newEnabled });
|
||||||
toast.success(newEnabled ? 'Skills enabled' : 'Skills disabled');
|
toast.success(newEnabled ? 'Skills enabled' : 'Skills disabled');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to update skills settings');
|
toast.error('Failed to update skills settings');
|
||||||
@@ -35,7 +38,12 @@ export function useSkillsSettings() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
throw new Error('Settings API not available');
|
||||||
|
}
|
||||||
await api.settings.updateGlobal({ skillsSources: newSources });
|
await api.settings.updateGlobal({ skillsSources: newSources });
|
||||||
|
// Update local store after successful server update
|
||||||
|
useAppStore.setState({ skillsSources: newSources });
|
||||||
toast.success('Skills sources updated');
|
toast.success('Skills sources updated');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to update skills sources');
|
toast.error('Failed to update skills sources');
|
||||||
|
|||||||
@@ -3,27 +3,28 @@
|
|||||||
*
|
*
|
||||||
* Provides read-only view of custom subagent configurations
|
* Provides read-only view of custom subagent configurations
|
||||||
* used for specialized task delegation. Supports:
|
* 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 (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 { useAppStore } from '@/store/app-store';
|
||||||
import type { AgentDefinition } from '@automaker/types';
|
import type { AgentDefinition } from '@automaker/types';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
|
||||||
export type SubagentScope = 'global' | 'project';
|
export type SubagentScope = 'global' | 'project';
|
||||||
export type SubagentType = 'programmatic' | 'filesystem';
|
export type SubagentType = 'filesystem';
|
||||||
export type FilesystemSource = 'user' | 'project';
|
export type FilesystemSource = 'user' | 'project';
|
||||||
|
|
||||||
export interface SubagentWithScope {
|
export interface SubagentWithScope {
|
||||||
name: string;
|
name: string;
|
||||||
definition: AgentDefinition;
|
definition: AgentDefinition;
|
||||||
scope: SubagentScope; // For programmatic agents
|
scope: SubagentScope;
|
||||||
type: SubagentType;
|
type: SubagentType;
|
||||||
// For filesystem agents:
|
source: FilesystemSource;
|
||||||
source?: FilesystemSource;
|
filePath: string;
|
||||||
filePath?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilesystemAgent {
|
interface FilesystemAgent {
|
||||||
@@ -34,71 +35,46 @@ interface FilesystemAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useSubagents() {
|
export function useSubagents() {
|
||||||
const { settings, currentProject, projectSettings } = useAppStore();
|
const currentProject = useAppStore((state) => state.currentProject);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [subagentsWithScope, setSubagentsWithScope] = useState<SubagentWithScope[]>([]);
|
const [subagentsWithScope, setSubagentsWithScope] = useState<SubagentWithScope[]>([]);
|
||||||
const [filesystemAgents, setFilesystemAgents] = useState<FilesystemAgent[]>([]);
|
|
||||||
|
|
||||||
// Fetch filesystem agents
|
// Fetch filesystem agents
|
||||||
const fetchFilesystemAgents = async () => {
|
const fetchFilesystemAgents = useCallback(async () => {
|
||||||
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
|
if (!api.settings) {
|
||||||
|
console.warn('Settings API not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const data = await api.settings.discoverAgents(currentProject?.path, ['user', 'project']);
|
const data = await api.settings.discoverAgents(currentProject?.path, ['user', 'project']);
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success && data.agents) {
|
||||||
setFilesystemAgents(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) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch filesystem agents:', error);
|
console.error('Failed to fetch filesystem agents:', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [currentProject?.path]);
|
||||||
|
|
||||||
// Fetch filesystem agents on mount and when project changes
|
// Fetch filesystem agents on mount and when project changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchFilesystemAgents();
|
fetchFilesystemAgents();
|
||||||
}, [currentProject?.path]);
|
}, [fetchFilesystemAgents]);
|
||||||
|
|
||||||
// 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]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subagentsWithScope,
|
subagentsWithScope,
|
||||||
|
|||||||
@@ -234,6 +234,8 @@ export async function syncSettingsToServer(): Promise<boolean> {
|
|||||||
keyboardShortcuts: state.keyboardShortcuts,
|
keyboardShortcuts: state.keyboardShortcuts,
|
||||||
aiProfiles: state.aiProfiles,
|
aiProfiles: state.aiProfiles,
|
||||||
mcpServers: state.mcpServers,
|
mcpServers: state.mcpServers,
|
||||||
|
enableSkills: state.enableSkills,
|
||||||
|
skillsSources: state.skillsSources,
|
||||||
promptCustomization: state.promptCustomization,
|
promptCustomization: state.promptCustomization,
|
||||||
projects: state.projects,
|
projects: state.projects,
|
||||||
trashedProjects: state.trashedProjects,
|
trashedProjects: state.trashedProjects,
|
||||||
|
|||||||
@@ -758,6 +758,83 @@ export interface ElectronAPI {
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
ideation?: IdeationAPI;
|
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
|
// Note: Window interface is declared in @/types/electron.d.ts
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ export interface AppState {
|
|||||||
// MCP Servers
|
// MCP Servers
|
||||||
mcpServers: MCPServerConfig[]; // List of configured MCP servers for agent use
|
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
|
// Prompt Customization
|
||||||
promptCustomization: PromptCustomization; // Custom prompts for Auto Mode, Agent, Backlog Plan, Enhancement
|
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)
|
enableSandboxMode: false, // Default to disabled (can be enabled for additional security)
|
||||||
skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog)
|
skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog)
|
||||||
mcpServers: [], // No MCP servers configured by default
|
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
|
promptCustomization: {}, // Empty by default - all prompts use built-in defaults
|
||||||
aiProfiles: DEFAULT_AI_PROFILES,
|
aiProfiles: DEFAULT_AI_PROFILES,
|
||||||
projectAnalysis: null,
|
projectAnalysis: null,
|
||||||
|
|||||||
Reference in New Issue
Block a user