코워크와 코드의 작업 폴더 선택 멈춤을 완화한다

- 작업 폴더 변경 직후 UI 스레드에서 실행되던 스킬 소스 재탐색을 백그라운드 재로드로 분리한다.

- 조건부 스킬 활성화 경로를 재로드와 분리해 첨부 파일 변경처럼 폴더가 바뀌지 않는 경우 불필요한 스킬 재탐색을 줄인다.

- README와 DEVELOPMENT 문서에 2026-04-14 19:02(KST) 기준 작업 이력과 검증 결과를 반영한다.

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_folderpick\\ -p:IntermediateOutputPath=obj\\verify_folderpick\\ 경고 0 / 오류 0
This commit is contained in:
2026-04-14 18:47:04 +09:00
parent 77495e6ac1
commit 4746d2834b
3 changed files with 85 additions and 10 deletions

View File

@@ -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);
}
/// <summary>
/// 현재 작업 컨텍스트(첨부 파일 + 작업 폴더) 기준으로
/// 조건부 paths 스킬 활성화를 갱신합니다.
/// </summary>
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();