From b2600086631fd016c889d892ca8b33dd707cac6b Mon Sep 17 00:00:00 2001 From: lacvet Date: Wed, 15 Apr 2026 14:18:14 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=94=EB=93=9C=20=ED=83=AD=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=ED=8F=B4=EB=8D=94=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=B6=88=EC=9D=BC=EC=B9=98=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B9=A0=EB=A5=B8=20=EC=A0=84=EC=86=A1=20=EC=95=88=EC=A0=95?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 작업 폴더 선택 시 현재 대화가 없더라도 Code/Cowork 대화를 즉시 확보하고 WorkFolder를 먼저 기록하도록 보강했다. 새 Code/Cowork 대화가 탭별 최근 작업 폴더를 기본으로 승계하도록 ChatSessionStateService를 조정하고, 현재 폴더 표시도 CodeWorkFolder/CoworkWorkFolder를 우선 사용하도록 맞췄다. 작업 폴더 해제 시 대화 메타데이터와 탭별 저장 폴더를 함께 초기화하도록 정리했다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_workfolder_sync\\ -p:IntermediateOutputPath=obj\\verify_workfolder_sync\\ / dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ChatSessionStateServiceTests -p:OutputPath=bin\\verify_workfolder_sync_tests\\ -p:IntermediateOutputPath=obj\\verify_workfolder_sync_tests\ --- README.md | 8 ++++++ docs/DEVELOPMENT.md | 8 ++++++ .../Services/ChatSessionStateServiceTests.cs | 15 +++++++++- .../Services/ChatSessionStateService.cs | 20 ++++++++++++- src/AxCopilot/Views/ChatWindow.xaml.cs | 28 +++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a8b7d52..5da66ba 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,14 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 `docs/claw-code-parity-plan.md` - 업데이트: 2026-04-15 10:57 (KST) +- 업데이트: 2026-04-15 15:09 (KST) +- Code/Cowork 작업 폴더 선택 직후 빠른 전송에서도 대화 메타데이터와 UI 표시가 어긋나지 않도록 정리했습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)는 폴더 선택 시 현재 대화가 없으면 즉시 생성해 `WorkFolder`를 먼저 반영하고, 현재 폴더 표시도 탭별 최근 폴더(`CodeWorkFolder`/`CoworkWorkFolder`)를 우선 읽도록 바꿨습니다. +- 새 Code/Cowork 대화도 최근 작업 폴더를 그대로 승계합니다. [ChatSessionStateService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/ChatSessionStateService.cs)는 `CreateFreshConversation(...)`에서 탭별 저장 폴더를 기본 `WorkFolder`로 채워, 화면에는 폴더가 보이는데 전송 시에는 경로가 없다고 막히는 불일치를 줄였습니다. +- 작업 폴더 지우기 동작도 같은 기준으로 맞췄습니다. 이제 폴더 해제 시 대화 메타데이터뿐 아니라 탭별 저장 폴더와 UI 상태도 함께 초기화되어, 이전 선택 경로가 fallback으로 다시 보이는 현상을 줄입니다. +- 테스트: [ChatSessionStateServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ChatSessionStateServiceTests.cs) 확장 +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_workfolder_sync\\ -p:IntermediateOutputPath=obj\\verify_workfolder_sync\\` 경고 0 / 오류 0 +- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatSessionStateServiceTests" -p:OutputPath=bin\\verify_workfolder_sync_tests\\ -p:IntermediateOutputPath=obj\\verify_workfolder_sync_tests\\` 통과 37 + - 업데이트: 2026-04-15 12:51 (KST) - AX Agent 진행 이력에 `1`, `[`, `file_read]` 같은 깨진 조각이 보이던 문제를 정리했습니다. 새 [AgentProgressSummarySanitizer.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentProgressSummarySanitizer.cs)가 스트리밍 미리보기, `Thinking` 요약, `[이전 도구 호출: ...]` transcript 꼬리 문자열을 공통 규칙으로 정제해 저품질 파편을 제거합니다. - [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs)는 스트리밍 `TextDelta` preview와 일반 `Thinking` emit 전에 정제기를 적용해, 의미 없는 단문은 이벤트 자체를 만들지 않도록 했습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index eff3767..d75c744 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1357,3 +1357,11 @@ UI ?붿옄???€洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾 ### 검증 - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_runtime_policy_alignment_build\\ -p:IntermediateOutputPath=obj\\verify_runtime_policy_alignment_build\\` 경고 0 / 오류 0 - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentLoopE2ETests|AgentLoopResponseClassificationServiceTests" -p:OutputPath=bin\\verify_runtime_policy_alignment\\ -p:IntermediateOutputPath=obj\\verify_runtime_policy_alignment\\` 통과 19 +업데이트: 2026-04-15 15:09 (KST) +- `src/AxCopilot/Views/ChatWindow.xaml.cs`의 작업 폴더 반영 경로를 보강했습니다. `SetWorkFolder(...)`가 이제 Code/Cowork 탭에서 현재 대화가 없더라도 `EnsureCurrentConversation(...)`으로 대화를 즉시 확보한 뒤 `WorkFolder`를 기록해, 폴더 선택 직후 빠른 전송에서도 대화 메타데이터가 비어 있지 않게 유지합니다. +- `GetCurrentWorkFolder()`는 전역 `Llm.WorkFolder`보다 탭별 `CodeWorkFolder`/`CoworkWorkFolder`를 먼저 읽도록 바꿨습니다. 폴더 바·워터마크·스킬 로더가 보는 경로와 실제 탭별 저장 경로를 더 잘 맞춰 UI fallback과 실행 경로가 어긋나는 상황을 줄였습니다. +- `BtnFolderClear_Click(...)`는 대화의 `WorkFolder`만 비우던 기존 동작에서 확장해 탭별 최근 작업 폴더 설정과 UI 상태도 함께 초기화합니다. 이전 경로가 설정 fallback으로 다시 나타나는 현상을 줄이고, 이후 스킬 재로드도 같은 기준으로 다시 시작합니다. +- `src/AxCopilot/Services/ChatSessionStateService.cs`의 `CreateFreshConversation(...)`는 Code/Cowork 탭 새 대화 생성 시 탭별 최근 작업 폴더를 기본 `WorkFolder`로 승계합니다. 이로써 “UI에는 폴더가 보이는데 전송 차단은 경로 없음으로 판단”하던 불일치를 완화합니다. +- 테스트: `src/AxCopilot.Tests/Services/ChatSessionStateServiceTests.cs`에서 fresh conversation 기본 폴더 승계와 탭별 우선순위를 검증하도록 확장 +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_workfolder_sync\\ -p:IntermediateOutputPath=obj\\verify_workfolder_sync\\` 경고 0 / 오류 0 +- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatSessionStateServiceTests" -p:OutputPath=bin\\verify_workfolder_sync_tests\\ -p:IntermediateOutputPath=obj\\verify_workfolder_sync_tests\\` 통과 37 diff --git a/src/AxCopilot.Tests/Services/ChatSessionStateServiceTests.cs b/src/AxCopilot.Tests/Services/ChatSessionStateServiceTests.cs index a2c339d..26ae433 100644 --- a/src/AxCopilot.Tests/Services/ChatSessionStateServiceTests.cs +++ b/src/AxCopilot.Tests/Services/ChatSessionStateServiceTests.cs @@ -617,10 +617,23 @@ public class ChatSessionStateServiceTests var conversation = session.CreateFreshConversation("Code", settings); conversation.Tab.Should().Be("Code"); - conversation.WorkFolder.Should().Be(""); + conversation.WorkFolder.Should().Be(@"E:\workspace"); session.CurrentConversation.Should().BeSameAs(conversation); } + [Fact] + public void CreateFreshConversation_PrefersTabSpecificWorkFolderOutsideChatTab() + { + var session = new ChatSessionStateService(); + var settings = new SettingsService(); + settings.Settings.Llm.WorkFolder = @"E:\global"; + settings.Settings.Llm.CodeWorkFolder = @"E:\code"; + + var conversation = session.CreateFreshConversation("Code", settings); + + conversation.WorkFolder.Should().Be(@"E:\code"); + } + [Fact] public void CreateBranchConversation_ClonesConversationContextUpToBranchPoint() { diff --git a/src/AxCopilot/Services/ChatSessionStateService.cs b/src/AxCopilot/Services/ChatSessionStateService.cs index 276ce3a..528d63c 100644 --- a/src/AxCopilot/Services/ChatSessionStateService.cs +++ b/src/AxCopilot/Services/ChatSessionStateService.cs @@ -168,7 +168,7 @@ public sealed class ChatSessionStateService if (string.Equals(normalizedTab, "Code", StringComparison.OrdinalIgnoreCase) || string.Equals(normalizedTab, "Cowork", StringComparison.OrdinalIgnoreCase)) { - created.WorkFolder = ""; + created.WorkFolder = ResolveDefaultWorkFolderForTab(normalizedTab, settings); try { var currentPerm = AxCopilot.Services.Agent.PermissionModeCatalog.NormalizeGlobalMode( @@ -187,6 +187,24 @@ public sealed class ChatSessionStateService return created; } + private static string ResolveDefaultWorkFolderForTab(string normalizedTab, ISettingsService settings) + { + var llm = settings.Settings.Llm; + var tabFolder = normalizedTab switch + { + "Code" => llm.CodeWorkFolder, + "Cowork" => llm.CoworkWorkFolder, + _ => "", + }; + + if (!string.IsNullOrWhiteSpace(tabFolder)) + return tabFolder; + + return string.Equals(normalizedTab, "Chat", StringComparison.OrdinalIgnoreCase) + ? "" + : llm.WorkFolder ?? ""; + } + public ChatConversation CreateBranchConversation( ChatConversation source, int atIndex, diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index bef50b7..87a3f59 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -2187,6 +2187,9 @@ public partial class ChatWindow : Window ChatConversation? convToPersist = null; lock (_convLock) { + if (_currentConversation == null && (_activeTab is "Cowork" or "Code")) + _currentConversation = ChatSession?.EnsureCurrentConversation(_activeTab) ?? new ChatConversation { Tab = _activeTab }; + if (_currentConversation != null) { var session = ChatSession; @@ -2240,6 +2243,15 @@ public partial class ChatWindow : Window if (_currentConversation != null && !string.IsNullOrEmpty(_currentConversation.WorkFolder)) return _currentConversation.WorkFolder; } + + if (string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrWhiteSpace(_settings.Settings.Llm.CodeWorkFolder)) + return _settings.Settings.Llm.CodeWorkFolder; + + if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrWhiteSpace(_settings.Settings.Llm.CoworkWorkFolder)) + return _settings.Settings.Llm.CoworkWorkFolder; + return _settings.Settings.Llm.WorkFolder; } @@ -2331,6 +2343,7 @@ public partial class ChatWindow : Window private void BtnFolderClear_Click(object sender, RoutedEventArgs e) { + var currentFolder = GetCurrentWorkFolder(); ViewModel.WorkFolder = ""; lock (_convLock) { @@ -2343,6 +2356,21 @@ public partial class ChatWindow : Window _currentConversation.WorkFolder = ""; } } + + if (string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase)) + _settings.Settings.Llm.CodeWorkFolder = ""; + else if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase)) + _settings.Settings.Llm.CoworkWorkFolder = ""; + + if (!string.IsNullOrWhiteSpace(currentFolder) + && string.Equals(_settings.Settings.Llm.WorkFolder, currentFolder, StringComparison.OrdinalIgnoreCase)) + _settings.Settings.Llm.WorkFolder = ""; + + ScheduleSettingsSave(); + RefreshContextUsageVisual(); + RefreshInputWatermarkText(); + UpdateFolderSelectButtonStyle(); + UpdateConditionalSkillActivation(reset: true, reloadSkillSources: true); } /// 현재 대화의 개별 설정을 로드합니다. null이면 전역 기본값 사용.