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:
2026-04-14 19:15:12 +09:00
parent 3747a92c12
commit 946c31e275
17 changed files with 956 additions and 81 deletions

View File

@@ -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)
{