스킬 런타임 2차 고도화와 도구 노출 필터 정비
프로젝트 .claude/skills 재귀 로드와 namespaced SKILL.md 파싱을 추가하고 번들/사용자/프로젝트 스킬을 함께 노출하도록 SkillService와 설정 UI를 확장했다. 슬래시 스킬 호출 시 인자 치환, 스킬 폴더 변수 치환, inline shell 실행, when_to_use 기반 자동 스킬 가이드를 실제 ChatWindow 런타임 경로에 연결했다. blanket deny 권한은 모델 노출 전 활성 도구 목록에서 먼저 제외하도록 AgentLoopService를 보강했고 관련 테스트와 README/DEVELOPMENT 문서를 업데이트했다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_phase2\\ -p:IntermediateOutputPath=obj\\verify_phase2\\ (경고 0 / 오류 0) 검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentToolCatalogTests|SkillServiceRuntimePolicyTests" -p:OutputPath=bin\\verify_phase2_tests\\ -p:IntermediateOutputPath=obj\\verify_phase2_tests\\ (통과 16, 기존 WorkspaceContextGeneratorTests nullable 경고 1건 유지)
This commit is contained in:
@@ -524,7 +524,7 @@ public partial class ChatWindow : Window
|
||||
if (_settings.Settings.Llm.EnableSkillSystem)
|
||||
{
|
||||
SkillService.EnsureSkillFolder();
|
||||
SkillService.LoadSkills(_settings.Settings.Llm.SkillsFolderPath);
|
||||
SkillService.LoadSkills(_settings.Settings.Llm.SkillsFolderPath, GetCurrentWorkFolder());
|
||||
UpdateConditionalSkillActivation(reset: true);
|
||||
}
|
||||
|
||||
@@ -2241,6 +2241,15 @@ public partial class ChatWindow : Window
|
||||
return _settings.Settings.Llm.WorkFolder;
|
||||
}
|
||||
|
||||
private void EnsureSkillSystemLoadedForCurrentWorkspace()
|
||||
{
|
||||
if (!_settings.Settings.Llm.EnableSkillSystem)
|
||||
return;
|
||||
|
||||
SkillService.EnsureSkillFolder();
|
||||
SkillService.LoadSkills(_settings.Settings.Llm.SkillsFolderPath, GetCurrentWorkFolder());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 작업 컨텍스트(첨부 파일 + 작업 폴더) 기준으로
|
||||
/// 조건부 paths 스킬 활성화를 갱신합니다.
|
||||
@@ -2248,6 +2257,7 @@ public partial class ChatWindow : Window
|
||||
private void UpdateConditionalSkillActivation(bool reset = false)
|
||||
{
|
||||
if (!_settings.Settings.Llm.EnableSkillSystem) return;
|
||||
EnsureSkillSystemLoadedForCurrentWorkspace();
|
||||
var cwd = GetCurrentWorkFolder();
|
||||
if (string.IsNullOrWhiteSpace(cwd) || !System.IO.Directory.Exists(cwd)) return;
|
||||
if (reset) SkillService.ResetConditionalSkillActivation();
|
||||
@@ -3460,6 +3470,7 @@ public partial class ChatWindow : Window
|
||||
// 스킬 슬래시 명령어 매칭 (탭별 필터)
|
||||
if (_settings.Settings.Llm.EnableSkillSystem)
|
||||
{
|
||||
EnsureSkillSystemLoadedForCurrentWorkspace();
|
||||
var skillMatches = SkillService.MatchSlashCommand(text)
|
||||
.Where(s => s.IsVisibleInTab(_activeTab))
|
||||
.Select(s => (Cmd: "/" + s.Name,
|
||||
@@ -3518,7 +3529,7 @@ public partial class ChatWindow : Window
|
||||
}
|
||||
|
||||
/// <summary>슬래시 명령어를 감지하여 시스템 프롬프트와 사용자 텍스트를 분리합니다.</summary>
|
||||
private (string? slashSystem, string userText) ParseSlashCommand(string input)
|
||||
private async Task<(string? slashSystem, string userText)> ParseSlashCommandAsync(string input, CancellationToken ct = default)
|
||||
{
|
||||
var trimmed = input.TrimStart();
|
||||
if (trimmed.StartsWith("/"))
|
||||
@@ -3535,17 +3546,10 @@ public partial class ChatWindow : Window
|
||||
}
|
||||
|
||||
// 스킬 명령어 매칭
|
||||
var matchedSkill = SkillService.MatchSlashInvocation(input);
|
||||
if (matchedSkill != null)
|
||||
{
|
||||
var slashCmd = "/" + matchedSkill.Name;
|
||||
var rest = input[slashCmd.Length..].Trim();
|
||||
var runtimePolicy = SkillService.BuildRuntimeDirective(matchedSkill);
|
||||
var mergedPrompt = string.IsNullOrWhiteSpace(runtimePolicy)
|
||||
? matchedSkill.SystemPrompt
|
||||
: $"{matchedSkill.SystemPrompt}\n\n{runtimePolicy}";
|
||||
return (mergedPrompt, string.IsNullOrEmpty(rest) ? matchedSkill.Label : rest);
|
||||
}
|
||||
EnsureSkillSystemLoadedForCurrentWorkspace();
|
||||
var compiledInvocation = await SkillService.BuildSlashInvocationAsync(input, GetCurrentWorkFolder(), ct);
|
||||
if (compiledInvocation != null)
|
||||
return (compiledInvocation.SystemPrompt, compiledInvocation.DisplayText);
|
||||
|
||||
return (null, input);
|
||||
}
|
||||
@@ -5004,7 +5008,7 @@ public partial class ChatWindow : Window
|
||||
{
|
||||
llm.EnableSkillSystem = true;
|
||||
SkillService.EnsureSkillFolder();
|
||||
SkillService.LoadSkills(llm.SkillsFolderPath);
|
||||
SkillService.LoadSkills(llm.SkillsFolderPath, GetCurrentWorkFolder());
|
||||
UpdateConditionalSkillActivation(reset: true);
|
||||
ScheduleSettingsSave();
|
||||
_appState.LoadFromSettings(_settings);
|
||||
@@ -5102,7 +5106,17 @@ public partial class ChatWindow : Window
|
||||
ClearPromptCardPlaceholder();
|
||||
|
||||
// 슬래시 명령어 처리
|
||||
var (slashSystem, displayText) = ParseSlashCommand(text);
|
||||
var (slashSystem, displayText) = await ParseSlashCommandAsync(text, CancellationToken.None);
|
||||
|
||||
if (slashSystem == null
|
||||
&& _settings.Settings.Llm.EnableSkillSystem
|
||||
&& !text.TrimStart().StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
EnsureSkillSystemLoadedForCurrentWorkspace();
|
||||
var autoSkillPrompt = await SkillService.BuildProactiveSkillSystemPromptAsync(displayText, _activeTab, GetCurrentWorkFolder(), CancellationToken.None);
|
||||
if (!string.IsNullOrWhiteSpace(autoSkillPrompt))
|
||||
slashSystem = autoSkillPrompt;
|
||||
}
|
||||
|
||||
if (string.Equals(slashSystem, "__CLEAR__", StringComparison.Ordinal))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user