From f53f35bbedbd44f36b92afe3c028c46d4e6eaff9 Mon Sep 17 00:00:00 2001 From: lacvet Date: Sun, 5 Apr 2026 14:12:22 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B3=84=ED=9A=8D=20=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=A0=9C=EA=B1=B0=EC=99=80=20=ED=94=8C?= =?UTF-8?q?=EB=9E=9C=20=EC=8A=B9=EC=9D=B8=20UX=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메인 설정과 AX Agent 내부 설정에서 계획 모드 UI를 숨기고 저장값을 항상 off로 고정했습니다. AgentLoop 런타임도 계획 모드를 off로 고정해 코워크와 코드에서 자동 계획 승인 팝업이 반복 노출되지 않도록 정리했습니다. PlanViewerWindow는 AX Agent 창 owner 리소스를 직접 받아 같은 테마 축을 따르도록 바꾸고 인라인 승인 버튼 중복 노출을 제거했습니다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 경고 0 오류 0 --- README.md | 4 +++ docs/DEVELOPMENT.md | 5 ++++ .../Services/Agent/AgentLoopService.cs | 15 +---------- src/AxCopilot/Services/AppStateService.cs | 2 +- src/AxCopilot/ViewModels/SettingsViewModel.cs | 11 ++++---- src/AxCopilot/Views/ChatWindow.xaml | 2 +- src/AxCopilot/Views/ChatWindow.xaml.cs | 20 +++++++-------- src/AxCopilot/Views/PlanViewerWindow.cs | 25 +++++++++++++------ src/AxCopilot/Views/SettingsWindow.xaml | 2 +- 9 files changed, 45 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 9e92d70..00c571e 100644 --- a/README.md +++ b/README.md @@ -806,6 +806,10 @@ ow + toggle 시각 언어로 통일했습니다. - 이제 `도구` 탭에서는 훅과 도구/커넥터 목록을, `스킬` 탭에서는 스킬 폴더, 슬래시 설정, 드래그 앤 드롭, 로드된 스킬, 폴백 모델, MCP 서버를, `차단` 탭에서는 차단 경로/확장자만 관리합니다. 같이 [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) 에서 메인 설정의 `AX Agent` 바로가기 탭을 좌측 사이드바 맨 아래로 재배치했습니다. - 런처 하단 바도 요소별로 제어할 수 있게 바꿨습니다. [AppSettings.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/AppSettings.cs), [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs), [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 에 `성능 / 포모도로 / 메모 / 날씨 / 일정 / 배터리` 하단 위젯 표시 토글을 추가해서 일반 설정에서 항목별로 바로 켜고 끌 수 있게 했습니다. - [LauncherWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.xaml), [LauncherWindow.Widgets.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/LauncherWindow.Widgets.cs) 에서는 `Ollama / API / MCP` 서버 상태 위젯을 런처 하단 기능에서 완전히 제거했고, 남은 위젯들만 설정값에 따라 실제 표시되도록 연결했습니다. 배터리 위젯도 노트북 상태와 사용자 토글을 함께 반영해 보이게 정리했습니다. +- `claw-code` 기준으로 계획 UX도 다시 눌렀습니다. [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs) 에서 저장된 `PlanMode` 값과 무관하게 런타임 계획 모드를 `off`로 고정해, 코워크/코드에서 매번 계획 승인 팝업이 뜨지 않도록 바꿨습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml), [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서는 메인 설정과 AX Agent 내부 설정의 `계획 모드` 행을 숨겼고, [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs), [AppStateService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AppStateService.cs) 에서도 항상 `off`만 저장/반영되게 정리했습니다. +- 계획 확인 팝업은 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs), [PlanViewerWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PlanViewerWindow.cs) 기준으로 AX Agent 창을 owner로 받아 리소스를 그대로 합치게 바꿨고, 채팅 본문에 별도 인라인 승인 버튼을 다시 꽂지 않도록 정리했습니다. +- 업데이트: 2026-04-05 16:20 (KST) - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 - 업데이트: 2026-04-05 15:16 (KST) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 2ad432a..b3e905e 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -4570,3 +4570,8 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - 배터리 위젯은 사용자 토글과 실제 배터리 가용 상태를 함께 반영하도록 `UpdateWidgetVisibility()`에서 최종 가시성을 결정하게 바꿨고, 모든 위젯이 꺼져 있으면 하단 위젯 바 전체도 자동으로 숨깁니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0 - 업데이트: 2026-04-05 15:16 (KST) +- `claw-code` 기준 계획 UX 정리도 반영했습니다. [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs) 의 `ResolveEffectivePlanMode(...)` 는 이제 항상 `off`를 반환해, 저장된 예전 계획 모드 값이 남아 있어도 런타임에서는 자동 계획/자동 승인 팝업이 다시 살아나지 않게 했습니다. +- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml) 과 [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 메인 설정과 AX Agent 내부 설정의 `계획 모드` UI를 `Collapsed`로 전환했고, [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs), [AppStateService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AppStateService.cs) 도 `off` 고정으로 바꿔 예전 persisted 값이 다시 UI나 상태바에 반영되지 않게 정리했습니다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 의 `CreatePlanDecisionCallback()`은 더 이상 채팅 본문에 인라인 승인 버튼을 추가하지 않고, [PlanViewerWindow.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/PlanViewerWindow.cs) 를 AX Agent 창 owner와 merged resources 기준으로 생성해 플랜 팝업이 AX Agent 테마와 같은 리소스 축을 따르도록 바꿨습니다. +- 검증 예정: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` +- 업데이트: 2026-04-05 16:20 (KST) diff --git a/src/AxCopilot/Services/Agent/AgentLoopService.cs b/src/AxCopilot/Services/Agent/AgentLoopService.cs index 94b0bea..3b45347 100644 --- a/src/AxCopilot/Services/Agent/AgentLoopService.cs +++ b/src/AxCopilot/Services/Agent/AgentLoopService.cs @@ -2098,20 +2098,7 @@ public partial class AgentLoopService internal static string ResolveEffectivePlanMode(string? configuredPlanMode, string? activeTab, string? taskType) { - var normalizedPlanMode = (configuredPlanMode ?? "off").Trim().ToLowerInvariant(); - if (normalizedPlanMode is not ("off" or "auto" or "always")) - normalizedPlanMode = "off"; - - if (string.Equals(activeTab, "Cowork", StringComparison.OrdinalIgnoreCase) - && string.Equals(taskType, "docs", StringComparison.OrdinalIgnoreCase) - && normalizedPlanMode == "off") - { - // claw-code 레퍼런스처럼 문서형 코워크는 실행 전에 구조를 먼저 세우고, - // 그 계획을 바탕으로 실제 산출물 생성 단계까지 이어가도록 기본값을 보정합니다. - return "always"; - } - - return normalizedPlanMode; + return "off"; } private static void InjectTaskTypeGuidance(List messages, TaskTypePolicy taskPolicy) diff --git a/src/AxCopilot/Services/AppStateService.cs b/src/AxCopilot/Services/AppStateService.cs index 91c28c3..89e199d 100644 --- a/src/AxCopilot/Services/AppStateService.cs +++ b/src/AxCopilot/Services/AppStateService.cs @@ -196,7 +196,7 @@ public sealed class AppStateService Permissions.FilePermission = PermissionModeCatalog.NormalizeGlobalMode(llm.FilePermission); Permissions.AgentDecisionLevel = llm.AgentDecisionLevel ?? "detailed"; - Permissions.PlanMode = llm.PlanMode ?? "off"; + Permissions.PlanMode = "off"; Permissions.ToolOverrideCount = llm.ToolPermissions?.Count ?? 0; Permissions.ToolOverrides = llm.ToolPermissions? .OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase) diff --git a/src/AxCopilot/ViewModels/SettingsViewModel.cs b/src/AxCopilot/ViewModels/SettingsViewModel.cs index 0af6500..be17317 100644 --- a/src/AxCopilot/ViewModels/SettingsViewModel.cs +++ b/src/AxCopilot/ViewModels/SettingsViewModel.cs @@ -370,11 +370,13 @@ public class SettingsViewModel : INotifyPropertyChanged set { _agentDecisionLevel = value; OnPropertyChanged(); } } - private string _planMode = "off"; public string PlanMode { - get => _planMode; - set { _planMode = value; OnPropertyChanged(); } + get => "off"; + set + { + OnPropertyChanged(); + } } private bool _enableMultiPassDocument; @@ -1138,7 +1140,6 @@ public class SettingsViewModel : INotifyPropertyChanged _planDiffSeverityMediumRatioPercent = llm.PlanDiffSeverityMediumRatioPercent > 0 ? Math.Clamp(llm.PlanDiffSeverityMediumRatioPercent, 1, 100) : 25; _planDiffSeverityHighRatioPercent = llm.PlanDiffSeverityHighRatioPercent > 0 ? Math.Clamp(llm.PlanDiffSeverityHighRatioPercent, 1, 100) : 60; _agentDecisionLevel = llm.AgentDecisionLevel; - _planMode = string.IsNullOrEmpty(llm.PlanMode) ? "off" : llm.PlanMode; _enableMultiPassDocument = llm.EnableMultiPassDocument; _enableCoworkVerification = llm.EnableCoworkVerification; _enableFilePathHighlight = llm.EnableFilePathHighlight; @@ -1580,7 +1581,7 @@ public class SettingsViewModel : INotifyPropertyChanged s.Llm.PlanDiffSeverityMediumRatioPercent = _planDiffSeverityMediumRatioPercent; s.Llm.PlanDiffSeverityHighRatioPercent = _planDiffSeverityHighRatioPercent; s.Llm.AgentDecisionLevel = _agentDecisionLevel; - s.Llm.PlanMode = _planMode; + s.Llm.PlanMode = "off"; s.Llm.EnableMultiPassDocument = _enableMultiPassDocument; s.Llm.EnableCoworkVerification = _enableCoworkVerification; s.Llm.EnableFilePathHighlight = _enableFilePathHighlight; diff --git a/src/AxCopilot/Views/ChatWindow.xaml b/src/AxCopilot/Views/ChatWindow.xaml index 30f43ad..c278bcb 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml +++ b/src/AxCopilot/Views/ChatWindow.xaml @@ -2880,7 +2880,7 @@ - + diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index 8736273..576c625 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -8735,7 +8735,8 @@ public partial class ChatWindow : Window sb.AppendLine("You are AX Copilot Agent. You can read, write, and edit files using the provided tools."); sb.AppendLine($"Today's date: {DateTime.Now:yyyy년 M월 d일} ({DateTime.Now:yyyy-MM-dd}, {DateTime.Now:dddd})."); sb.AppendLine("Available skills: excel_create (.xlsx), docx_create (.docx), csv_create (.csv), markdown_create (.md), html_create (.html), script_create (.bat/.ps1), document_review (품질 검증), format_convert (포맷 변환)."); - sb.AppendLine("Always produce a concrete execution plan before major Cowork tasks. For document/report/proposal/manual requests, create a 3-7 step plan first, then execute it."); + sb.AppendLine("Only present a step-by-step execution plan when the user explicitly asks for a plan or when the session is already in plan mode."); + sb.AppendLine("For ordinary Cowork requests, proceed directly with the work instead of stopping for plan approval."); sb.AppendLine("After creating files, summarize what was created and include the actual output path."); sb.AppendLine("Do not stop after a single step. Continue autonomously until the request is completed or a concrete blocker (permission denial, missing dependency, hard error) is encountered."); sb.AppendLine("When adapting external references, rewrite names/structure/comments to AX Copilot style. Avoid clone-like outputs."); @@ -9858,7 +9859,7 @@ public partial class ChatWindow : Window // PlanViewerWindow 생성 또는 재사용 if (_planViewerWindow == null || !IsWindowAlive(_planViewerWindow)) { - _planViewerWindow = new PlanViewerWindow(); + _planViewerWindow = new PlanViewerWindow(this); _planViewerWindow.Closing += (_, e) => { e.Cancel = true; @@ -9869,9 +9870,6 @@ public partial class ChatWindow : Window // 계획 표시 + 승인 대기 _planViewerWindow.ShowPlanAsync(planSummary, steps, tcs); - // 채팅 창에 간략 배너 추가 + 인라인 승인 버튼도 표시 - AddDecisionButtons(tcs, options); - // 하단 바 계획 버튼 표시 ShowPlanButton(true); }); @@ -14091,7 +14089,7 @@ public partial class ChatWindow : Window BtnInlineFastMode.Content = GetQuickActionLabel("Fast", llm.FreeTierMode ? "켜짐" : "꺼짐"); BtnInlineReasoning.Content = GetQuickActionLabel("추론", ReasoningLabel(llm.AgentDecisionLevel)); - BtnInlinePlanMode.Content = GetQuickActionLabel("계획", PlanModeLabel(llm.PlanMode)); + BtnInlinePlanMode.Content = GetQuickActionLabel("계획", PlanModeLabel("off")); BtnInlinePermission.Content = GetQuickActionLabel("권한", PermissionModeCatalog.ToDisplayLabel(llm.FilePermission)); BtnInlineSkill.Content = $"스킬 · {(llm.EnableSkillSystem ? "On" : "Off")}"; BtnInlineCommandBrowser.Content = "명령/스킬 브라우저"; @@ -14102,7 +14100,7 @@ public partial class ChatWindow : Window ApplyQuickActionVisual(BtnInlineFastMode, llm.FreeTierMode, "#ECFDF5", "#166534"); ApplyQuickActionVisual(BtnInlineReasoning, !string.Equals(llm.AgentDecisionLevel, "normal", StringComparison.OrdinalIgnoreCase), "#EEF2FF", "#1D4ED8"); - ApplyQuickActionVisual(BtnInlinePlanMode, !string.Equals(llm.PlanMode, "off", StringComparison.OrdinalIgnoreCase), "#EEF2FF", "#4338CA"); + ApplyQuickActionVisual(BtnInlinePlanMode, false, "#EEF2FF", "#4338CA"); ApplyQuickActionVisual(BtnInlinePermission, !string.Equals(PermissionModeCatalog.NormalizeGlobalMode(llm.FilePermission), PermissionModeCatalog.Deny, StringComparison.OrdinalIgnoreCase), "#FFF7ED", @@ -16337,7 +16335,7 @@ public partial class ChatWindow : Window SelectComboTag(CmbOverlayOperationMode, OperationModePolicy.Normalize(_settings.Settings.OperationMode)); SelectComboTag(CmbOverlayFolderDataUsage, _folderDataUsage); SelectComboTag(CmbOverlayPermission, PermissionModeCatalog.NormalizeGlobalMode(llm.FilePermission)); - SelectComboTag(CmbOverlayPlanMode, llm.PlanMode); + SelectComboTag(CmbOverlayPlanMode, "off"); SelectComboTag(CmbOverlayReasoning, llm.AgentDecisionLevel); SelectComboTag(CmbOverlayFastMode, llm.FreeTierMode ? "on" : "off"); SelectComboTag(CmbOverlayDefaultOutputFormat, llm.DefaultOutputFormat ?? "auto"); @@ -16835,10 +16833,10 @@ public partial class ChatWindow : Window private void CmbOverlayPlanMode_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (_isOverlaySettingsSyncing || CmbOverlayPlanMode.SelectedItem is not ComboBoxItem selected || selected.Tag is not string tag) + if (_isOverlaySettingsSyncing) return; - _settings.Settings.Llm.PlanMode = tag; + _settings.Settings.Llm.PlanMode = "off"; PersistOverlaySettingsState(refreshOverlayDeferredInputs: false); } @@ -17051,7 +17049,7 @@ public partial class ChatWindow : Window private void BtnInlinePlanMode_Click(object sender, RoutedEventArgs e) { var llm = _settings.Settings.Llm; - llm.PlanMode = NextPlanMode(llm.PlanMode); + llm.PlanMode = "off"; _settings.Save(); _appState.LoadFromSettings(_settings); RefreshInlineSettingsPanel(); diff --git a/src/AxCopilot/Views/PlanViewerWindow.cs b/src/AxCopilot/Views/PlanViewerWindow.cs index 1f8cbcb..dcc023c 100644 --- a/src/AxCopilot/Views/PlanViewerWindow.cs +++ b/src/AxCopilot/Views/PlanViewerWindow.cs @@ -49,28 +49,37 @@ internal sealed class PlanViewerWindow : Window private bool _isExecuting; private readonly string _uiExpressionLevel; - public PlanViewerWindow() + public PlanViewerWindow(Window? owner = null) { + if (owner != null) + { + Owner = owner; + WindowStartupLocation = WindowStartupLocation.CenterOwner; + Resources.MergedDictionaries.Add(owner.Resources); + } + _uiExpressionLevel = ResolveUiExpressionLevel(); Width = 640; Height = 520; MinWidth = 480; MinHeight = 360; - WindowStartupLocation = WindowStartupLocation.CenterScreen; + WindowStartupLocation = owner == null + ? WindowStartupLocation.CenterScreen + : WindowStartupLocation.CenterOwner; WindowStyle = WindowStyle.None; AllowsTransparency = true; Background = Brushes.Transparent; ResizeMode = ResizeMode.CanResize; // WndProc로 직접 처리 ShowInTaskbar = false; - var bgBrush = Application.Current.TryFindResource("LauncherBackground") as Brush + var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)); - var primaryText = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White; - var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; - var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush + var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; + var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; + var accentBrush = TryFindResource("AccentColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC)); - var borderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray; + var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var root = new Border { @@ -151,7 +160,7 @@ internal sealed class PlanViewerWindow : Window mainGrid.Children.Add(_statusBar); // ── 툴바: 모두 열기 / 모두 닫기 ── - var hoverBgTb = Application.Current.TryFindResource("ItemHoverBackground") as Brush + var hoverBgTb = TryFindResource("ItemHoverBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var toolBar = new StackPanel { diff --git a/src/AxCopilot/Views/SettingsWindow.xaml b/src/AxCopilot/Views/SettingsWindow.xaml index 42a27a9..fe06430 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml +++ b/src/AxCopilot/Views/SettingsWindow.xaml @@ -4653,7 +4653,7 @@ - +