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

3
.claude/.gitignore vendored
View File

@@ -1 +1,2 @@
hans/
hans/
skills/

View File

@@ -0,0 +1,257 @@
/**
* Agent Discovery - Scans filesystem for AGENT.md files
*
* Discovers agents from:
* - ~/.claude/agents/ (user-level, global)
* - .claude/agents/ (project-level)
*
* Similar to Skills, but for custom subagents defined in AGENT.md files.
*/
import path from 'path';
import os from 'os';
import { createLogger } from '@automaker/utils';
import { secureFs, systemPaths } from '@automaker/platform';
import type { AgentDefinition } from '@automaker/types';
const logger = createLogger('AgentDiscovery');
export interface FilesystemAgent {
name: string; // Directory name (e.g., 'code-reviewer')
definition: AgentDefinition;
source: 'user' | 'project';
filePath: string; // Full path to AGENT.md
}
/**
* Parse agent content string into AgentDefinition
* Format:
* ---
* name: agent-name # Optional
* description: When to use this agent
* tools: tool1, tool2, tool3 # Optional (comma or space separated list)
* model: sonnet # Optional: sonnet, opus, haiku
* ---
* System prompt content here...
*/
function parseAgentContent(content: string, filePath: string): AgentDefinition | null {
// Extract frontmatter
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!frontmatterMatch) {
logger.warn(`Invalid agent file format (missing frontmatter): ${filePath}`);
return null;
}
const [, frontmatter, prompt] = frontmatterMatch;
// Parse description (required)
const description = frontmatter.match(/description:\s*(.+)/)?.[1]?.trim();
if (!description) {
logger.warn(`Missing description in agent file: ${filePath}`);
return null;
}
// Parse tools (optional) - supports both comma-separated and space-separated
const toolsMatch = frontmatter.match(/tools:\s*(.+)/);
const tools = toolsMatch
? toolsMatch[1]
.split(/[,\s]+/) // Split by comma or whitespace
.map((t) => t.trim())
.filter((t) => t && t !== '')
: undefined;
// Parse model (optional) - validate against allowed values
const modelMatch = frontmatter.match(/model:\s*(\w+)/);
const modelValue = modelMatch?.[1]?.trim();
const validModels = ['sonnet', 'opus', 'haiku', 'inherit'] as const;
const model =
modelValue && validModels.includes(modelValue as (typeof validModels)[number])
? (modelValue as 'sonnet' | 'opus' | 'haiku' | 'inherit')
: undefined;
if (modelValue && !model) {
logger.warn(
`Invalid model "${modelValue}" in agent file: ${filePath}. Expected one of: ${validModels.join(', ')}`
);
}
return {
description,
prompt: prompt.trim(),
tools,
model,
};
}
/**
* Directory entry with type information
*/
interface DirEntry {
name: string;
isFile: boolean;
isDirectory: boolean;
}
/**
* Filesystem adapter interface for abstracting systemPaths vs secureFs
*/
interface FsAdapter {
exists: (filePath: string) => Promise<boolean>;
readdir: (dirPath: string) => Promise<DirEntry[]>;
readFile: (filePath: string) => Promise<string>;
}
/**
* Create a filesystem adapter for system paths (user directory)
*/
function createSystemPathAdapter(): FsAdapter {
return {
exists: (filePath) => Promise.resolve(systemPaths.systemPathExists(filePath)),
readdir: async (dirPath) => {
const entryNames = await systemPaths.systemPathReaddir(dirPath);
const entries: DirEntry[] = [];
for (const name of entryNames) {
const stat = await systemPaths.systemPathStat(path.join(dirPath, name));
entries.push({
name,
isFile: stat.isFile(),
isDirectory: stat.isDirectory(),
});
}
return entries;
},
readFile: (filePath) => systemPaths.systemPathReadFile(filePath, 'utf-8') as Promise<string>,
};
}
/**
* Create a filesystem adapter for project paths (secureFs)
*/
function createSecureFsAdapter(): FsAdapter {
return {
exists: (filePath) =>
secureFs
.access(filePath)
.then(() => true)
.catch(() => false),
readdir: async (dirPath) => {
const entries = await secureFs.readdir(dirPath, { withFileTypes: true });
return entries.map((entry) => ({
name: entry.name,
isFile: entry.isFile(),
isDirectory: entry.isDirectory(),
}));
},
readFile: (filePath) => secureFs.readFile(filePath, 'utf-8') as Promise<string>,
};
}
/**
* Parse agent file using the provided filesystem adapter
*/
async function parseAgentFileWithAdapter(
filePath: string,
fsAdapter: FsAdapter
): Promise<AgentDefinition | null> {
try {
const content = await fsAdapter.readFile(filePath);
return parseAgentContent(content, filePath);
} catch (error) {
logger.error(`Failed to parse agent file: ${filePath}`, error);
return null;
}
}
/**
* Scan a directory for agent .md files
* Agents can be in two formats:
* 1. Flat: agent-name.md (file directly in agents/)
* 2. Subdirectory: agent-name/AGENT.md (folder + file, similar to Skills)
*/
async function scanAgentsDirectory(
baseDir: string,
source: 'user' | 'project'
): Promise<FilesystemAgent[]> {
const agents: FilesystemAgent[] = [];
const fsAdapter = source === 'user' ? createSystemPathAdapter() : createSecureFsAdapter();
try {
// Check if directory exists
const exists = await fsAdapter.exists(baseDir);
if (!exists) {
logger.debug(`Directory does not exist: ${baseDir}`);
return agents;
}
// Read all entries in the directory
const entries = await fsAdapter.readdir(baseDir);
for (const entry of entries) {
// Check for flat .md file format (agent-name.md)
if (entry.isFile && entry.name.endsWith('.md')) {
const agentName = entry.name.slice(0, -3); // Remove .md extension
const agentFilePath = path.join(baseDir, entry.name);
const definition = await parseAgentFileWithAdapter(agentFilePath, fsAdapter);
if (definition) {
agents.push({
name: agentName,
definition,
source,
filePath: agentFilePath,
});
logger.debug(`Discovered ${source} agent (flat): ${agentName}`);
}
}
// Check for subdirectory format (agent-name/AGENT.md)
else if (entry.isDirectory) {
const agentFilePath = path.join(baseDir, entry.name, 'AGENT.md');
const agentFileExists = await fsAdapter.exists(agentFilePath);
if (agentFileExists) {
const definition = await parseAgentFileWithAdapter(agentFilePath, fsAdapter);
if (definition) {
agents.push({
name: entry.name,
definition,
source,
filePath: agentFilePath,
});
logger.debug(`Discovered ${source} agent (subdirectory): ${entry.name}`);
}
}
}
}
} catch (error) {
logger.error(`Failed to scan agents directory: ${baseDir}`, error);
}
return agents;
}
/**
* Discover all filesystem-based agents from user and project sources
*/
export async function discoverFilesystemAgents(
projectPath?: string,
sources: Array<'user' | 'project'> = ['user', 'project']
): Promise<FilesystemAgent[]> {
const agents: FilesystemAgent[] = [];
// Discover user-level agents from ~/.claude/agents/
if (sources.includes('user')) {
const userAgentsDir = path.join(os.homedir(), '.claude', 'agents');
const userAgents = await scanAgentsDirectory(userAgentsDir, 'user');
agents.push(...userAgents);
logger.info(`Discovered ${userAgents.length} user-level agents from ${userAgentsDir}`);
}
// Discover project-level agents from .claude/agents/
if (sources.includes('project') && projectPath) {
const projectAgentsDir = path.join(projectPath, '.claude', 'agents');
const projectAgents = await scanAgentsDirectory(projectAgentsDir, 'project');
agents.push(...projectAgents);
logger.info(`Discovered ${projectAgents.length} project-level agents from ${projectAgentsDir}`);
}
return agents;
}

View File

@@ -241,3 +241,83 @@ export async function getPromptCustomization(
enhancement: mergeEnhancementPrompts(customization.enhancement),
};
}
/**
* Get Skills configuration from settings.
* Returns configuration for enabling skills and which sources to load from.
*
* @param settingsService - Settings service instance
* @returns Skills configuration with enabled state, sources, and tool inclusion flag
*/
export async function getSkillsConfiguration(settingsService: SettingsService): Promise<{
enabled: boolean;
sources: Array<'user' | 'project'>;
shouldIncludeInTools: boolean;
}> {
const settings = await settingsService.getGlobalSettings();
const enabled = settings.enableSkills ?? true; // Default enabled
const sources = settings.skillsSources ?? ['user', 'project']; // Default both sources
return {
enabled,
sources,
shouldIncludeInTools: enabled && sources.length > 0,
};
}
/**
* 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.
*
* @param settingsService - Settings service instance
* @param projectPath - Path to the project for loading project-specific subagents
* @returns Record of agent names to definitions, or undefined if none configured
*/
export async function getCustomSubagents(
settingsService: SettingsService,
projectPath?: string
): Promise<Record<string, import('@automaker/types').AgentDefinition> | undefined> {
// Get global subagents
const globalSettings = await settingsService.getGlobalSettings();
const globalSubagents = globalSettings.customSubagents || {};
// If no project path, return only global subagents
if (!projectPath) {
return Object.keys(globalSubagents).length > 0 ? globalSubagents : undefined;
}
// Get project-specific subagents
const projectSettings = await settingsService.getProjectSettings(projectPath);
const projectSubagents = projectSettings.customSubagents || {};
// Merge: project-level takes precedence
const merged = {
...globalSubagents,
...projectSubagents,
};
return Object.keys(merged).length > 0 ? merged : undefined;
}

View File

@@ -97,6 +97,8 @@ export class ClaudeProvider extends BaseProvider {
...(options.mcpServers && { mcpServers: options.mcpServers }),
// Extended thinking configuration
...(maxThinkingTokens && { maxThinkingTokens }),
// Subagents configuration for specialized task delegation
...(options.agents && { agents: options.agents }),
};
// Build prompt payload

View File

@@ -23,6 +23,7 @@ import { createGetProjectHandler } from './routes/get-project.js';
import { createUpdateProjectHandler } from './routes/update-project.js';
import { createMigrateHandler } from './routes/migrate.js';
import { createStatusHandler } from './routes/status.js';
import { createDiscoverAgentsHandler } from './routes/discover-agents.js';
/**
* Create settings router with all endpoints
@@ -39,6 +40,7 @@ import { createStatusHandler } from './routes/status.js';
* - POST /project - Get project settings (requires projectPath in body)
* - PUT /project - Update project settings
* - POST /migrate - Migrate settings from localStorage
* - POST /agents/discover - Discover filesystem agents from .claude/agents/ (read-only)
*
* @param settingsService - Instance of SettingsService for file I/O
* @returns Express Router configured with all settings endpoints
@@ -72,5 +74,8 @@ export function createSettingsRoutes(settingsService: SettingsService): Router {
// Migration from localStorage
router.post('/migrate', createMigrateHandler(settingsService));
// Filesystem agents discovery (read-only)
router.post('/agents/discover', createDiscoverAgentsHandler());
return router;
}

View File

@@ -0,0 +1,61 @@
/**
* Discover Agents Route - Returns filesystem-based agents from .claude/agents/
*
* Scans both user-level (~/.claude/agents/) and project-level (.claude/agents/)
* directories for AGENT.md files and returns parsed agent definitions.
*/
import type { Request, Response } from 'express';
import { discoverFilesystemAgents } from '../../../lib/agent-discovery.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('DiscoverAgentsRoute');
interface DiscoverAgentsRequest {
projectPath?: string;
sources?: Array<'user' | 'project'>;
}
/**
* Create handler for discovering filesystem agents
*
* POST /api/settings/agents/discover
* Body: { projectPath?: string, sources?: ['user', 'project'] }
*
* Returns:
* {
* success: true,
* agents: Array<{
* name: string,
* definition: AgentDefinition,
* source: 'user' | 'project',
* filePath: string
* }>
* }
*/
export function createDiscoverAgentsHandler() {
return async (req: Request, res: Response) => {
try {
const { projectPath, sources = ['user', 'project'] } = req.body as DiscoverAgentsRequest;
logger.info(
`Discovering agents from sources: ${sources.join(', ')}${projectPath ? ` (project: ${projectPath})` : ''}`
);
const agents = await discoverFilesystemAgents(projectPath, sources);
logger.info(`Discovered ${agents.length} filesystem agents`);
res.json({
success: true,
agents,
});
} catch (error) {
logger.error('Failed to discover agents:', error);
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Failed to discover agents',
});
}
};
}

View File

@@ -26,6 +26,9 @@ import {
filterClaudeMdFromContext,
getMCPServersFromSettings,
getPromptCustomization,
getSkillsConfiguration,
getSubagentsConfiguration,
getCustomSubagents,
} from '../lib/settings-helpers.js';
interface Message {
@@ -255,6 +258,22 @@ export class AgentService {
// Load MCP servers from settings (global setting only)
const mcpServers = await getMCPServersFromSettings(this.settingsService, '[AgentService]');
// Get Skills configuration from settings
const skillsConfig = this.settingsService
? await getSkillsConfiguration(this.settingsService)
: { enabled: false, sources: [] as Array<'user' | 'project'>, shouldIncludeInTools: false };
// 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({
projectPath: effectiveWorkDir,
@@ -289,7 +308,47 @@ export class AgentService {
// Extract model, maxTurns, and allowedTools from SDK options
const effectiveModel = sdkOptions.model!;
const maxTurns = sdkOptions.maxTurns;
const allowedTools = sdkOptions.allowedTools as string[] | undefined;
let allowedTools = sdkOptions.allowedTools as string[] | undefined;
// Build merged settingSources array using Set for automatic deduplication
const sdkSettingSources = (sdkOptions.settingSources ?? []).filter(
(source): source is 'user' | 'project' => source === 'user' || source === 'project'
);
const skillSettingSources = skillsConfig.enabled ? skillsConfig.sources : [];
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 (needsSkillTool && !allowedTools.includes('Skill')) {
allowedTools.push('Skill');
}
// Add Task tool if custom subagents are configured
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');
}
}
// Get provider for this model (with prefix)
const provider = ProviderFactory.getProviderForModel(effectiveModel);
@@ -308,9 +367,10 @@ export class AgentService {
allowedTools: allowedTools,
abortController: session.abortController!,
conversationHistory: conversationHistory.length > 0 ? conversationHistory : undefined,
settingSources: sdkOptions.settingSources,
settingSources: settingSources.length > 0 ? settingSources : undefined,
sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, // Pass MCP servers configuration
agents: customSubagents, // Pass custom subagents for task delegation
thinkingLevel: effectiveThinkingLevel, // Pass thinking level for Claude models
reasoningEffort: effectiveReasoningEffort, // Pass reasoning effort for Codex models
};

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,

View File

@@ -17,6 +17,7 @@ export type {
McpStdioServerConfig,
McpSSEServerConfig,
McpHttpServerConfig,
AgentDefinition,
ReasoningEffort,
} from './provider.js';

View File

@@ -76,6 +76,20 @@ export interface McpHttpServerConfig {
headers?: Record<string, string>;
}
/**
* Subagent definition for specialized task delegation
*/
export interface AgentDefinition {
/** Natural language description of when to use this agent */
description: string;
/** System prompt defining the agent's role and behavior */
prompt: string;
/** Restricted tool list (if omitted, inherits all tools) */
tools?: string[];
/** Model override for this agent */
model?: 'sonnet' | 'opus' | 'haiku' | 'inherit';
}
/**
* Options for executing a query via a provider
*/
@@ -110,6 +124,11 @@ export interface ExecuteOptions {
* Only applies to Claude models; Cursor models handle thinking internally.
*/
thinkingLevel?: ThinkingLevel;
/**
* Custom subagents for specialized task delegation
* Key: agent name, Value: agent definition
*/
agents?: Record<string, AgentDefinition>;
/**
* Reasoning effort for Codex/OpenAI models with reasoning capabilities.
* Controls how many reasoning tokens the model generates before responding.

View File

@@ -543,6 +543,43 @@ export interface GlobalSettings {
// Prompt Customization
/** Custom prompts for Auto Mode, Agent Runner, Backlog Planning, and Enhancements */
promptCustomization?: PromptCustomization;
// Skills Configuration
/**
* Enable Skills functionality (loads from .claude/skills/ directories)
* @default true
*/
enableSkills?: boolean;
/**
* Which directories to load Skills from
* - 'user': ~/.claude/skills/ (personal skills)
* - 'project': .claude/skills/ (project-specific skills)
* @default ['user', 'project']
*/
skillsSources?: Array<'user' | 'project'>;
// Subagents Configuration
/**
* Enable Custom Subagents functionality (loads from .claude/agents/ directories)
* @default true
*/
enableSubagents?: boolean;
/**
* Which directories to load Subagents from
* - 'user': ~/.claude/agents/ (personal agents)
* - 'project': .claude/agents/ (project-specific agents)
* @default ['user', 'project']
*/
subagentsSources?: Array<'user' | 'project'>;
/**
* Custom subagent definitions for specialized task delegation (programmatic)
* Key: agent name (e.g., 'code-reviewer', 'test-runner')
* Value: agent configuration
*/
customSubagents?: Record<string, import('./provider.js').AgentDefinition>;
}
/**
@@ -642,6 +679,15 @@ export interface ProjectSettings {
// Claude Agent SDK Settings
/** Auto-load CLAUDE.md files using SDK's settingSources option (project override) */
autoLoadClaudeMd?: boolean;
// Subagents Configuration
/**
* Project-specific custom subagent definitions for specialized task delegation
* Merged with global customSubagents, project-level takes precedence
* Key: agent name (e.g., 'code-reviewer', 'test-runner')
* Value: agent configuration
*/
customSubagents?: Record<string, import('./provider.js').AgentDefinition>;
}
/**
@@ -743,6 +789,10 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
codexAdditionalDirs: DEFAULT_CODEX_ADDITIONAL_DIRS,
codexThreadId: undefined,
mcpServers: [],
enableSkills: true,
skillsSources: ['user', 'project'],
enableSubagents: true,
subagentsSources: ['user', 'project'],
};
/** Default credentials (empty strings - user must provide API keys) */