AX Agent ?? ?? ??? MCP ?? ??? ???? ??? ??? ??
- MCP ?? ?????? synthetic skill? ???? McpSkillCatalog? ???? ToolRegistry ?? snapshot ?? ??? ??? - managed/user/additional/project/plugin/mcp/legacy ?? source ??, plugin-only ??, source? inline shell trust boundary? SkillService/AppSettings/Settings UI? ??? - SlashCommandCatalog? ChatWindow?? builtin command? skill? ???? ???? ??? ?? ? ????? dedupe?? MCP ???? ? synthetic skill ?? ??? SkillGallery/AgentSettings? ??? - README.md? docs/DEVELOPMENT.md? 2026-04-14 19:13 (KST) ?? ?? ??? ?? ??? ??? - ??: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_phase4\\ -p:IntermediateOutputPath=obj\\verify_phase4\\ (?? 0, ?? 0) - ??: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SkillServiceRuntimePolicyTests|SlashCommandCatalogTests|McpSkillCatalogTests" -p:OutputPath=bin\\verify_phase4_tests\\ -p:IntermediateOutputPath=obj\\verify_phase4_tests\\ (?? 17, ?? WorkspaceContextGeneratorTests.cs nullable ?? 1? ??)
This commit is contained in:
@@ -869,6 +869,42 @@
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="기본 제공 스킬 소스"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableManagedSkillSource"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="사용자 스킬 소스"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableUserSkillSource"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="보조 스킬 폴더 탐색"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableAdditionalSkillDiscovery"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -893,6 +929,30 @@
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="MCP 스킬 탐색"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnableMcpSkillDiscovery"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="플러그인 전용 모드"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkEnablePluginOnlySkillMode"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -917,6 +977,30 @@
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="플러그인 inline shell 허용"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkAllowPluginSkillInlineShell"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="MCP inline shell 허용"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<CheckBox x:Name="ChkAllowMcpSkillInlineShell"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"/>
|
||||
</Grid>
|
||||
<StackPanel Margin="0,12,0,0">
|
||||
<TextBlock Text="보조 스킬 폴더 목록"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
@@ -1053,7 +1137,7 @@
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryText}"/>
|
||||
<TextBlock Text="현재 AX Agent에서 사용할 수 있는 슬래시 스킬 목록입니다."
|
||||
<TextBlock Text="현재 AX Agent에서 사용할 수 있는 슬래시 스킬 목록입니다. 프로젝트 `.claude/skills`와 MCP 스킬 소스도 함께 반영됩니다."
|
||||
Margin="0,4,0,8"
|
||||
FontSize="11.5"
|
||||
Foreground="{DynamicResource SecondaryText}"/>
|
||||
|
||||
@@ -66,10 +66,17 @@ public partial class AgentSettingsWindow : Window
|
||||
ChkEnableToolHooks.IsChecked = _llm.EnableToolHooks;
|
||||
ChkEnableHookInputMutation.IsChecked = _llm.EnableHookInputMutation;
|
||||
ChkEnableHookPermissionUpdate.IsChecked = _llm.EnableHookPermissionUpdate;
|
||||
ChkEnableManagedSkillSource.IsChecked = _llm.EnableManagedSkillSource;
|
||||
ChkEnableUserSkillSource.IsChecked = _llm.EnableUserSkillSource;
|
||||
ChkEnableAdditionalSkillDiscovery.IsChecked = _llm.EnableAdditionalSkillDiscovery;
|
||||
ChkEnableProjectSkillDiscovery.IsChecked = _llm.EnableProjectSkillDiscovery;
|
||||
ChkEnablePluginSkillDiscovery.IsChecked = _llm.EnablePluginSkillDiscovery;
|
||||
ChkEnableMcpSkillDiscovery.IsChecked = _llm.EnableMcpSkillDiscovery;
|
||||
ChkEnablePluginOnlySkillMode.IsChecked = _llm.EnablePluginOnlySkillMode;
|
||||
ChkEnableLegacyCommandSkills.IsChecked = _llm.EnableLegacyCommandSkills;
|
||||
ChkEnableSkillInlineShell.IsChecked = _llm.EnableSkillInlineShell;
|
||||
ChkAllowPluginSkillInlineShell.IsChecked = _llm.AllowPluginSkillInlineShell;
|
||||
ChkAllowMcpSkillInlineShell.IsChecked = _llm.AllowMcpSkillInlineShell;
|
||||
ChkEnableCoworkVerification.IsChecked = _llm.EnableCoworkVerification;
|
||||
ChkEnableProjectRules.IsChecked = _llm.EnableProjectRules;
|
||||
ChkEnableAgentMemory.IsChecked = _llm.EnableAgentMemory;
|
||||
@@ -550,10 +557,17 @@ public partial class AgentSettingsWindow : Window
|
||||
_llm.EnableToolHooks = ChkEnableToolHooks.IsChecked == true;
|
||||
_llm.EnableHookInputMutation = ChkEnableHookInputMutation.IsChecked == true;
|
||||
_llm.EnableHookPermissionUpdate = ChkEnableHookPermissionUpdate.IsChecked == true;
|
||||
_llm.EnableManagedSkillSource = ChkEnableManagedSkillSource.IsChecked == true;
|
||||
_llm.EnableUserSkillSource = ChkEnableUserSkillSource.IsChecked == true;
|
||||
_llm.EnableAdditionalSkillDiscovery = ChkEnableAdditionalSkillDiscovery.IsChecked == true;
|
||||
_llm.EnableProjectSkillDiscovery = ChkEnableProjectSkillDiscovery.IsChecked == true;
|
||||
_llm.EnablePluginSkillDiscovery = ChkEnablePluginSkillDiscovery.IsChecked == true;
|
||||
_llm.EnableMcpSkillDiscovery = ChkEnableMcpSkillDiscovery.IsChecked == true;
|
||||
_llm.EnablePluginOnlySkillMode = ChkEnablePluginOnlySkillMode.IsChecked == true;
|
||||
_llm.EnableLegacyCommandSkills = ChkEnableLegacyCommandSkills.IsChecked == true;
|
||||
_llm.EnableSkillInlineShell = ChkEnableSkillInlineShell.IsChecked == true;
|
||||
_llm.AllowPluginSkillInlineShell = ChkAllowPluginSkillInlineShell.IsChecked == true;
|
||||
_llm.AllowMcpSkillInlineShell = ChkAllowMcpSkillInlineShell.IsChecked == true;
|
||||
_llm.EnableCoworkVerification = ChkEnableCoworkVerification.IsChecked == true;
|
||||
_llm.EnableProjectRules = ChkEnableProjectRules.IsChecked == true;
|
||||
_llm.EnableAgentMemory = ChkEnableAgentMemory.IsChecked == true;
|
||||
@@ -656,7 +670,7 @@ public partial class AgentSettingsWindow : Window
|
||||
Padding = new Thickness(12, 10, 12, 10),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = "로드된 스킬이 없습니다. 기본/추가 스킬 폴더 또는 프로젝트 `.claude/skills` 아래에 `.skill.md`나 `SKILL.md`를 추가한 뒤 저장하면 다시 불러옵니다.",
|
||||
Text = "로드된 스킬이 없습니다. 기본/추가 스킬 폴더, 프로젝트 `.claude/skills`, 연결된 MCP 스킬 소스를 확인한 뒤 다시 불러오세요.",
|
||||
FontSize = 11,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
@@ -671,10 +685,11 @@ public partial class AgentSettingsWindow : Window
|
||||
new { Title = "기본 제공 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "managed", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
new { Title = "프로젝트 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
new { Title = "플러그인 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
new { Title = "MCP 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "mcp", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
new { Title = "보조/공용 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
new { Title = "레거시 명령 스킬", Items = skills.Where(s => string.Equals(s.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
new { Title = "사용자 스킬", Items = skills.Where(s => !string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "managed", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(s.Requires)).ToList() },
|
||||
new { Title = "고급 스킬", Items = skills.Where(s => !string.IsNullOrWhiteSpace(s.Requires) && !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
new { Title = "사용자 스킬", Items = skills.Where(s => !string.Equals(s.SourceScope, "bundled", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "managed", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "mcp", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "additional", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(s.Requires)).ToList() },
|
||||
new { Title = "고급 스킬", Items = skills.Where(s => !string.IsNullOrWhiteSpace(s.Requires) && !string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase) && !string.Equals(s.SourceScope, "mcp", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||
};
|
||||
|
||||
foreach (var group in groups)
|
||||
|
||||
@@ -3529,8 +3529,13 @@ public partial class ChatWindow : Window
|
||||
// 탭별 필터링: Chat → "all"만, Cowork/Code → "all" + "dev"
|
||||
bool isDev = _activeTab is "Cowork" or "Code";
|
||||
|
||||
// 내장 슬래시 명령어 매칭 (탭 필터)
|
||||
var matches = SlashCommandCatalog.MatchBuiltinCommands(text, isDev);
|
||||
var slashEntries = SlashCommandCatalog.MatchBuiltinCommands(text, isDev)
|
||||
.Select(match => (
|
||||
match.Cmd,
|
||||
match.Label,
|
||||
match.IsSkill,
|
||||
Priority: SlashCommandCatalog.GetBuiltInCommandPriority(match.Cmd)))
|
||||
.ToList();
|
||||
|
||||
// 스킬 슬래시 명령어 매칭 (탭별 필터)
|
||||
if (_settings.Settings.Llm.EnableSkillSystem)
|
||||
@@ -3540,11 +3545,12 @@ public partial class ChatWindow : Window
|
||||
.Where(s => s.IsVisibleInTab(_activeTab))
|
||||
.Select(s => (Cmd: "/" + s.Name,
|
||||
Label: BuildSlashSkillLabel(s),
|
||||
IsSkill: true, Available: s.IsAvailable));
|
||||
foreach (var sm in skillMatches)
|
||||
matches.Add((sm.Cmd, sm.Label, sm.IsSkill));
|
||||
IsSkill: true,
|
||||
Priority: SkillService.GetSkillSourcePriority(s.SourceScope)));
|
||||
slashEntries.AddRange(skillMatches);
|
||||
}
|
||||
|
||||
var matches = SlashCommandCatalog.ComposeMatches(slashEntries);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
_slashPalette.Matches = matches;
|
||||
|
||||
@@ -4364,6 +4364,39 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="기본 제공 스킬 소스"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="앱에 함께 배포되는 관리형 스킬 파일 소스를 로드합니다. 번들 스킬은 항상 유지됩니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EnableManagedSkillSource, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="사용자 스킬 소스"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="%APPDATA%\AxCopilot\skills\와 직접 지정한 기본 스킬 폴더를 함께 로드합니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EnableUserSkillSource, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="보조 스킬 폴더 탐색"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="보조 스킬 폴더 목록에 입력한 공용/팀 스킬 폴더를 실제 로딩 대상에 포함합니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EnableAdditionalSkillDiscovery, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
@@ -4386,6 +4419,28 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="MCP 스킬 탐색"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="연결된 MCP 서버의 리소스/도구 메타데이터를 보조 스킬처럼 노출합니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EnableMcpSkillDiscovery, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="플러그인 전용 스킬 모드"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="활성화하면 기본 제공/플러그인 스킬만 노출하고 다른 소스는 숨깁니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EnablePluginOnlySkillMode, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
@@ -4408,6 +4463,28 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="플러그인 inline shell 허용"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="플러그인 소스 스킬에 한해 inline shell 실행을 별도로 허용합니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding AllowPluginSkillInlineShell, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left" Margin="0,0,60,0">
|
||||
<TextBlock Style="{StaticResource RowLabel}" Text="MCP inline shell 허용"/>
|
||||
<TextBlock Style="{StaticResource RowHint}" Text="MCP 소스 스킬에는 기본적으로 inline shell을 막고, 필요 시에만 별도 허용합니다."/>
|
||||
</StackPanel>
|
||||
<CheckBox Style="{StaticResource ToggleSwitch}" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
IsChecked="{Binding AllowMcpSkillInlineShell, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource SettingsRow}">
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
|
||||
@@ -147,6 +147,7 @@ public partial class SkillGalleryWindow : Window
|
||||
"기본 제공" => skills.Where(IsBuiltInSkill).ToList(),
|
||||
"프로젝트" => skills.Where(s => string.Equals(s.SourceScope, "project", StringComparison.OrdinalIgnoreCase)).ToList(),
|
||||
"플러그인" => skills.Where(s => string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)).ToList(),
|
||||
"MCP" => skills.Where(s => string.Equals(s.SourceScope, "mcp", StringComparison.OrdinalIgnoreCase)).ToList(),
|
||||
"고급 (런타임)" => skills.Where(s => !string.IsNullOrEmpty(s.Requires)).ToList(),
|
||||
"사용자" => skills.Where(IsUserOwnedSkill).ToList(),
|
||||
_ => skills.ToList(),
|
||||
@@ -164,6 +165,8 @@ public partial class SkillGalleryWindow : Window
|
||||
var isBuiltIn = IsBuiltInSkill(skill);
|
||||
var isProject = string.Equals(skill.SourceScope, "project", StringComparison.OrdinalIgnoreCase);
|
||||
var isPlugin = string.Equals(skill.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase);
|
||||
var isMcp = string.Equals(skill.SourceScope, "mcp", StringComparison.OrdinalIgnoreCase);
|
||||
var hasBackingFile = !string.IsNullOrWhiteSpace(skill.FilePath) && File.Exists(skill.FilePath);
|
||||
|
||||
var card = new Border
|
||||
{
|
||||
@@ -231,6 +234,8 @@ public partial class SkillGalleryWindow : Window
|
||||
nameRow.Children.Add(MakeBadge("프로젝트", "#2563EB"));
|
||||
else if (isPlugin)
|
||||
nameRow.Children.Add(MakeBadge("플러그인", "#EC4899"));
|
||||
else if (isMcp)
|
||||
nameRow.Children.Add(MakeBadge("MCP", "#14B8A6"));
|
||||
else if (isUser)
|
||||
nameRow.Children.Add(MakeBadge("사용자", "#34D399"));
|
||||
else if (isAdvanced)
|
||||
@@ -266,62 +271,68 @@ public partial class SkillGalleryWindow : Window
|
||||
};
|
||||
|
||||
// 편집
|
||||
actions.Children.Add(MakeActionBtn("\uE70F", "#3B82F6", isUser ? "편집 (시각적 편집기)" : "편집 (파일 열기)",
|
||||
() =>
|
||||
{
|
||||
if (isUser)
|
||||
if (isUser || hasBackingFile)
|
||||
{
|
||||
actions.Children.Add(MakeActionBtn("\uE70F", "#3B82F6", isUser ? "편집 (시각적 편집기)" : "편집 (파일 열기)",
|
||||
() =>
|
||||
{
|
||||
var editor = new SkillEditorWindow(skill) { Owner = this };
|
||||
if (editor.ShowDialog() == true)
|
||||
if (isUser)
|
||||
{
|
||||
var editor = new SkillEditorWindow(skill) { Owner = this };
|
||||
if (editor.ShowDialog() == true)
|
||||
{
|
||||
SkillService.ReloadFromCurrentSettings();
|
||||
BuildCategoryFilter();
|
||||
RenderSkills();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(skill.FilePath) { UseShellExecute = true }); }
|
||||
catch (Exception ex) { CustomMessageBox.Show($"파일을 열 수 없습니다: {ex.Message}", "편집"); }
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 복제 (파일 기반 스킬만)
|
||||
if (hasBackingFile)
|
||||
{
|
||||
actions.Children.Add(MakeActionBtn("\uE8C8", "#10B981", "복제",
|
||||
() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var destFolder = Path.Combine(userFolder);
|
||||
if (!Directory.Exists(destFolder)) Directory.CreateDirectory(destFolder);
|
||||
|
||||
var srcName = Path.GetFileNameWithoutExtension(skill.FilePath);
|
||||
var destPath = Path.Combine(destFolder, $"{srcName}_copy.skill.md");
|
||||
var counter = 2;
|
||||
while (File.Exists(destPath))
|
||||
destPath = Path.Combine(destFolder, $"{srcName}_copy{counter++}.skill.md");
|
||||
|
||||
File.Copy(skill.FilePath, destPath);
|
||||
SkillService.ReloadFromCurrentSettings();
|
||||
BuildCategoryFilter();
|
||||
RenderSkills();
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex) { CustomMessageBox.Show($"복제 실패: {ex.Message}", "복제"); }
|
||||
}));
|
||||
|
||||
// 내보내기
|
||||
actions.Children.Add(MakeActionBtn("\uEDE1", "#F59E0B", "내보내기 (.zip)",
|
||||
() =>
|
||||
{
|
||||
try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(skill.FilePath) { UseShellExecute = true }); }
|
||||
catch (Exception ex) { CustomMessageBox.Show($"파일을 열 수 없습니다: {ex.Message}", "편집"); }
|
||||
}
|
||||
}));
|
||||
|
||||
// 복제 (사용자 스킬/폴더 스킬만)
|
||||
actions.Children.Add(MakeActionBtn("\uE8C8", "#10B981", "복제",
|
||||
() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var destFolder = Path.Combine(userFolder);
|
||||
if (!Directory.Exists(destFolder)) Directory.CreateDirectory(destFolder);
|
||||
|
||||
var srcName = Path.GetFileNameWithoutExtension(skill.FilePath);
|
||||
var destPath = Path.Combine(destFolder, $"{srcName}_copy.skill.md");
|
||||
var counter = 2;
|
||||
while (File.Exists(destPath))
|
||||
destPath = Path.Combine(destFolder, $"{srcName}_copy{counter++}.skill.md");
|
||||
|
||||
File.Copy(skill.FilePath, destPath);
|
||||
SkillService.ReloadFromCurrentSettings();
|
||||
BuildCategoryFilter();
|
||||
RenderSkills();
|
||||
}
|
||||
catch (Exception ex) { CustomMessageBox.Show($"복제 실패: {ex.Message}", "복제"); }
|
||||
}));
|
||||
|
||||
// 내보내기
|
||||
actions.Children.Add(MakeActionBtn("\uEDE1", "#F59E0B", "내보내기 (.zip)",
|
||||
() =>
|
||||
{
|
||||
var folderDlg = new System.Windows.Forms.FolderBrowserDialog
|
||||
{ Description = "내보낼 폴더를 선택하세요" };
|
||||
if (folderDlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||
var result = SkillService.ExportSkill(skill, folderDlg.SelectedPath);
|
||||
if (result != null)
|
||||
CustomMessageBox.Show($"내보내기 완료:\n{result}", "내보내기");
|
||||
else
|
||||
CustomMessageBox.Show("내보내기에 실패했습니다.", "내보내기");
|
||||
}));
|
||||
var folderDlg = new System.Windows.Forms.FolderBrowserDialog
|
||||
{ Description = "내보낼 폴더를 선택하세요" };
|
||||
if (folderDlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||
var result = SkillService.ExportSkill(skill, folderDlg.SelectedPath);
|
||||
if (result != null)
|
||||
CustomMessageBox.Show($"내보내기 완료:\n{result}", "내보내기");
|
||||
else
|
||||
CustomMessageBox.Show("내보내기에 실패했습니다.", "내보내기");
|
||||
}));
|
||||
}
|
||||
|
||||
// 삭제 (사용자 스킬만)
|
||||
if (isUser)
|
||||
@@ -405,6 +416,8 @@ public partial class SkillGalleryWindow : Window
|
||||
yield return "프로젝트";
|
||||
if (skills.Any(s => string.Equals(s.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)))
|
||||
yield return "플러그인";
|
||||
if (skills.Any(s => string.Equals(s.SourceScope, "mcp", StringComparison.OrdinalIgnoreCase)))
|
||||
yield return "MCP";
|
||||
if (skills.Any(s => !string.IsNullOrEmpty(s.Requires)))
|
||||
yield return "고급 (런타임)";
|
||||
if (skills.Any(IsUserOwnedSkill))
|
||||
@@ -422,7 +435,8 @@ public partial class SkillGalleryWindow : Window
|
||||
string.Equals(skill.SourceScope, "legacy", StringComparison.OrdinalIgnoreCase) ||
|
||||
(!IsBuiltInSkill(skill)
|
||||
&& !string.Equals(skill.SourceScope, "project", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(skill.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase));
|
||||
&& !string.Equals(skill.SourceScope, "plugin", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(skill.SourceScope, "mcp", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private Border MakeActionBtn(string icon, string colorHex, string tooltip, Action action)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
namespace AxCopilot.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
internal static class SlashCommandCatalog
|
||||
{
|
||||
@@ -108,6 +112,37 @@ internal static class SlashCommandCatalog
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal static int GetBuiltInCommandPriority(string commandToken)
|
||||
{
|
||||
if (!Commands.TryGetValue(commandToken, out var entry))
|
||||
return 2000;
|
||||
|
||||
return string.Equals(entry.Tab, "dev", StringComparison.OrdinalIgnoreCase)
|
||||
? 2100
|
||||
: 2000;
|
||||
}
|
||||
|
||||
internal static List<(string Cmd, string Label, bool IsSkill)> ComposeMatches(
|
||||
IEnumerable<(string Cmd, string Label, bool IsSkill, int Priority)> entries)
|
||||
{
|
||||
if (entries == null)
|
||||
return [];
|
||||
|
||||
return entries
|
||||
.Where(entry => !string.IsNullOrWhiteSpace(entry.Cmd))
|
||||
.GroupBy(entry => entry.Cmd, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(group => group
|
||||
.OrderByDescending(entry => entry.Priority)
|
||||
.ThenBy(entry => entry.IsSkill ? 1 : 0)
|
||||
.ThenBy(entry => entry.Cmd, StringComparer.OrdinalIgnoreCase)
|
||||
.First())
|
||||
.Select(entry => (entry.Cmd, entry.Label, entry.IsSkill, entry.Priority))
|
||||
.OrderByDescending(entry => entry.Priority)
|
||||
.ThenBy(entry => entry.Cmd, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(entry => (entry.Cmd, entry.Label, entry.IsSkill))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal static bool TryGetEntry(string commandToken, out (string Label, string SystemPrompt, string Tab) entry)
|
||||
=> Commands.TryGetValue(commandToken, out entry);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user