using System.Text; using System.Text.Json; namespace AxCopilot.Services.Agent; /// /// 스킬 관리 에이전트 도구. /// 로드된 스킬 목록 조회, 스킬 정보 확인, 스킬 실행을 지원합니다. /// public class SkillManagerTool : IAgentTool { public string Name => "skill_manager"; public string Description => "마크다운 기반 스킬(워크플로우)을 관리합니다.\n" + "- list: 사용 가능한 스킬 목록 조회\n" + "- info: 특정 스킬의 상세 정보 확인\n" + "- reload: 스킬 폴더를 다시 스캔하여 새 스킬 로드"; public ToolParameterSchema Parameters => new() { Properties = new() { ["action"] = new ToolProperty { Type = "string", Description = "list (목록), info (상세정보), reload (재로드)", Enum = ["list", "info", "reload"] }, ["skill_name"] = new ToolProperty { Type = "string", Description = "스킬 이름 (info 액션에서 사용)" }, }, Required = ["action"] }; public async Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default) { var app = System.Windows.Application.Current as App; if (!(app?.SettingsService?.Settings.Llm.EnableSkillSystem ?? true)) return ToolResult.Ok("스킬 시스템이 비활성 상태입니다. 설정 → AX Agent → 공통에서 활성화하세요."); var action = args.TryGetProperty("action", out var a) ? a.GetString() ?? "" : ""; var skillName = args.TryGetProperty("skill_name", out var s) ? s.GetString() ?? "" : ""; return action switch { "list" => ListSkills(), "info" => InfoSkill(skillName), "reload" => ReloadSkills(app), _ => ToolResult.Fail($"지원하지 않는 action: {action}. list, info, reload 중 선택하세요.") }; } private static ToolResult ListSkills() { var skills = SkillService.Skills; if (skills.Count == 0) return ToolResult.Ok("로드된 스킬이 없습니다. %APPDATA%\\AxCopilot\\skills\\에 *.skill.md 파일을 추가하세요."); var sb = new StringBuilder(); sb.AppendLine($"사용 가능한 스킬 ({skills.Count}개):\n"); foreach (var skill in skills) { var execBadge = string.Equals(skill.ExecutionContext, "fork", StringComparison.OrdinalIgnoreCase) ? "[FORK]" : "[DIRECT]"; sb.AppendLine($" /{skill.Name} {execBadge} — {skill.Label}"); sb.AppendLine($" {skill.Description}"); if (string.Equals(skill.ExecutionContext, "fork", StringComparison.OrdinalIgnoreCase)) sb.AppendLine(" 실행 방식: 위임 우선 (spawn_agent → wait_agents)"); if (!string.IsNullOrWhiteSpace(skill.AllowedTools)) sb.AppendLine($" allowed-tools: {skill.AllowedTools}"); if (!string.IsNullOrWhiteSpace(skill.Hooks)) sb.AppendLine($" hooks: {skill.Hooks}"); if (!string.IsNullOrWhiteSpace(skill.HookFilters)) sb.AppendLine($" hook-filters: {skill.HookFilters}"); sb.AppendLine(); } sb.AppendLine("슬래시 명령어(/{name})로 호출하거나, 대화에서 해당 워크플로우를 요청할 수 있습니다."); return ToolResult.Ok(sb.ToString()); } private static ToolResult InfoSkill(string name) { if (string.IsNullOrEmpty(name)) return ToolResult.Fail("skill_name이 필요합니다."); var skill = SkillService.Find(name); if (skill == null) return ToolResult.Fail($"'{name}' 스킬을 찾을 수 없습니다. skill_manager(action: list)로 목록을 확인하세요."); var sb = new StringBuilder(); sb.AppendLine($"스킬 상세: {skill.Label} (/{skill.Name})"); sb.AppendLine($"설명: {skill.Description}"); sb.AppendLine($"파일: {skill.FilePath}"); if (string.Equals(skill.ExecutionContext, "fork", StringComparison.OrdinalIgnoreCase)) sb.AppendLine("실행 배지: [FORK] · 위임 우선 실행"); else sb.AppendLine("실행 배지: [DIRECT] · 일반 실행"); if (!string.IsNullOrWhiteSpace(skill.ExecutionContext)) sb.AppendLine($"context: {skill.ExecutionContext}"); if (!string.IsNullOrWhiteSpace(skill.Agent)) sb.AppendLine($"agent: {skill.Agent}"); if (!string.IsNullOrWhiteSpace(skill.Effort)) sb.AppendLine($"effort: {skill.Effort}"); if (!string.IsNullOrWhiteSpace(skill.Model)) sb.AppendLine($"model: {skill.Model}"); if (skill.DisableModelInvocation) sb.AppendLine("disable-model-invocation: true"); if (!string.IsNullOrWhiteSpace(skill.AllowedTools)) sb.AppendLine($"allowed-tools: {skill.AllowedTools}"); if (!string.IsNullOrWhiteSpace(skill.Hooks)) sb.AppendLine($"hooks: {skill.Hooks}"); if (!string.IsNullOrWhiteSpace(skill.HookFilters)) sb.AppendLine($"hook-filters: {skill.HookFilters}"); var runtimeDirective = SkillService.BuildRuntimeDirective(skill); if (!string.IsNullOrWhiteSpace(runtimeDirective)) sb.AppendLine($"\n--- 런타임 정책 ---\n{runtimeDirective}"); sb.AppendLine($"\n--- 시스템 프롬프트 ---\n{skill.SystemPrompt}"); return ToolResult.Ok(sb.ToString()); } private static ToolResult ReloadSkills(App? app) { var customFolder = app?.SettingsService?.Settings.Llm.SkillsFolderPath ?? ""; SkillService.LoadSkills(customFolder); return ToolResult.Ok($"스킬 재로드 완료. {SkillService.Skills.Count}개 로드됨."); } }