diff --git a/README.md b/README.md index bddb958..1d4a73d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-14 19:02 (KST) +- 코워크/코드의 작업 폴더 선택 후 UI가 2~3초 멈추던 흐름을 점검해, 폴더 변경 직후 실행되던 스킬 소스 재탐색을 UI 스레드 밖으로 분리했습니다. 이제 작업 폴더 변경, 탭 전환, 대화 복원 시 필요한 스킬 재로드는 백그라운드에서 수행하고, 조건부 스킬 활성화만 UI에 다시 반영합니다. +- 첨부 파일 추가/제거처럼 작업 폴더가 바뀌지 않는 경로는 기존 스킬 집합만 기준으로 조건부 스킬을 갱신하도록 분리해 불필요한 재탐색도 줄였습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_folderpick\\ -p:IntermediateOutputPath=obj\\verify_folderpick\\` 경고 0 / 오류 0 + - 업데이트: 2026-04-14 18:45 (KST) - AX Agent 내부 설정의 스킬 탭 안내 블록에 커스텀 라벨을 추가했습니다. 이제 `.claude/skills/.../SKILL.md` 프로젝트 호환 경로가 스킬 탭 첫 화면에서 바로 보여, 워크스페이스에 같은 구조가 있으면 AX가 함께 읽는다는 점을 UI에서도 확인할 수 있습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_skilllabel\\ -p:IntermediateOutputPath=obj\\verify_skilllabel\\` 경고 0 / 오류 0 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index c4b7dd1..aadf08b 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -835,3 +835,8 @@ UI 디자인 대규모 리팩토링 등 위험 작업 전 기록한 안전 복 - 업데이트: 2026-04-14 18:45 (KST) - AX Agent 내부 설정의 스킬 탭 안내 블록에 커스텀 라벨을 추가했습니다. 이제 .claude/skills/.../SKILL.md 프로젝트 호환 경로가 스킬 탭 첫 화면에서 바로 보여, 워크스페이스에 같은 구조가 있으면 AX가 함께 읽는다는 점을 UI에서도 확인할 수 있습니다. - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_skilllabel\\ -p:IntermediateOutputPath=obj\\verify_skilllabel\\ 경고 0 / 오류 0 + +- 업데이트: 2026-04-14 19:02 (KST) +- 코워크/코드의 작업 폴더 선택 후 UI가 2~3초 멈추던 흐름을 점검해, 폴더 변경 직후 실행되던 스킬 소스 재탐색을 UI 스레드 밖으로 분리했습니다. 이제 작업 폴더 변경, 탭 전환, 대화 복원 시 필요한 스킬 재로드는 백그라운드에서 수행하고, 조건부 스킬 활성화만 UI에 다시 반영합니다. +- 첨부 파일 추가/제거처럼 작업 폴더가 바뀌지 않는 경로는 기존 스킬 집합만 기준으로 조건부 스킬을 갱신하도록 분리해 불필요한 재탐색도 줄였습니다. +- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_folderpick\\ -p:IntermediateOutputPath=obj\\verify_folderpick\\ 경고 0 / 오류 0 diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index 40eca5d..71f0884 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -130,6 +130,8 @@ public partial class ChatWindow : Window private readonly DispatcherTimer _agentProgressHintTimer; private readonly DispatcherTimer _tokenUsagePopupCloseTimer; private readonly DispatcherTimer _responsiveLayoutTimer; + private CancellationTokenSource? _workspaceSkillRefreshCts; + private int _workspaceSkillRefreshVersion; private CancellationTokenSource? _gitStatusRefreshCts; private int _displayedLength; // 현재 화면에 표시된 글자 수 private ResourceDictionary? _agentThemeDictionary; @@ -1833,7 +1835,7 @@ public partial class ChatWindow : Window UpdateChatTitle(); RefreshConversationList(); UpdateFolderBar(); - UpdateConditionalSkillActivation(reset: true); + UpdateConditionalSkillActivation(reset: true, reloadSkillSources: true); Services.LogService.Info($"[SwitchTab] NO_SESSION done, emptyState={EmptyState.Visibility}"); } @@ -2228,7 +2230,7 @@ public partial class ChatWindow : Window UpdateFolderSelectButtonStyle(); ScheduleGitBranchRefresh(); - UpdateConditionalSkillActivation(reset: true); + UpdateConditionalSkillActivation(reset: true, reloadSkillSources: true); } private string GetCurrentWorkFolder() @@ -2250,18 +2252,81 @@ public partial class ChatWindow : Window SkillService.LoadSkills(_settings.Settings.Llm.SkillsFolderPath, GetCurrentWorkFolder(), _settings.Settings.Llm.AdditionalSkillFolders); } + private void ScheduleWorkspaceSkillRefresh(bool reset = false) + { + if (!_settings.Settings.Llm.EnableSkillSystem) + return; + + var version = Interlocked.Increment(ref _workspaceSkillRefreshVersion); + _workspaceSkillRefreshCts?.Cancel(); + _workspaceSkillRefreshCts?.Dispose(); + _workspaceSkillRefreshCts = new CancellationTokenSource(); + var ct = _workspaceSkillRefreshCts.Token; + + var skillFolder = _settings.Settings.Llm.SkillsFolderPath; + var workFolder = GetCurrentWorkFolder(); + var additionalFolders = (_settings.Settings.Llm.AdditionalSkillFolders ?? []) + .Where(path => !string.IsNullOrWhiteSpace(path)) + .ToArray(); + + _ = Task.Run(() => + { + try + { + ct.ThrowIfCancellationRequested(); + SkillService.EnsureSkillFolder(); + SkillService.LoadSkills(skillFolder, workFolder, additionalFolders); + ct.ThrowIfCancellationRequested(); + + Dispatcher.BeginInvoke(new Action(() => + { + if (ct.IsCancellationRequested || version != _workspaceSkillRefreshVersion) + return; + + ApplyConditionalSkillActivation(reset, workFolder); + }), DispatcherPriority.Background); + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + Services.LogService.Debug($"작업 폴더 스킬 재로드 실패: {ex.Message}"); + } + }, ct); + } + + private void ApplyConditionalSkillActivation(bool reset = false, string? cwd = null) + { + if (!_settings.Settings.Llm.EnableSkillSystem) + return; + + cwd ??= GetCurrentWorkFolder(); + if (string.IsNullOrWhiteSpace(cwd) || !System.IO.Directory.Exists(cwd)) + return; + + if (reset) + SkillService.ResetConditionalSkillActivation(); + + SkillService.ActivateConditionalSkillsForPaths(_attachedFiles, cwd); + } + /// /// 현재 작업 컨텍스트(첨부 파일 + 작업 폴더) 기준으로 /// 조건부 paths 스킬 활성화를 갱신합니다. /// - private void UpdateConditionalSkillActivation(bool reset = false) + private void UpdateConditionalSkillActivation(bool reset = false, bool reloadSkillSources = 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(); - SkillService.ActivateConditionalSkillsForPaths(_attachedFiles, cwd); + if (!_settings.Settings.Llm.EnableSkillSystem) + return; + + if (reloadSkillSources) + { + ScheduleWorkspaceSkillRefresh(reset); + return; + } + + ApplyConditionalSkillActivation(reset); } private void BtnFolderClear_Click(object sender, RoutedEventArgs e) @@ -3102,7 +3167,7 @@ public partial class ChatWindow : Window UpdateChatTitle(); UpdateFolderBar(); UpdateSelectedPresetGuide(); - UpdateConditionalSkillActivation(reset: true); + UpdateConditionalSkillActivation(reset: true, reloadSkillSources: true); RenderMessages(); RefreshConversationList(); BuildTopicButtons();