diff --git a/README.md b/README.md index 4588399..2647c1b 100644 --- a/README.md +++ b/README.md @@ -1168,3 +1168,7 @@ MIT License - `claw-code` 기준 transcript 품질 향상을 위해 권한 요청/도구 결과/도구 이름 display catalog를 다시 정리했다. [AgentTranscriptDisplayCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs)는 파일/문서/빌드/Git/웹/스킬/질문 카테고리를 더 명확한 한국어 display name과 badge label로 분류하고, [PermissionRequestPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs)는 `명령 실행 / 웹 요청 / 스킬 실행 / 의견 요청 / 파일 수정 / 파일 접근` 권한 요청을 타입별 presentation으로 나누도록 보강했다. - [ToolResultPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs)는 `success / error / reject / cancel`을 도구 종류에 따라 `파일 작업 완료`, `빌드/테스트 실패`, `웹 요청 거부`처럼 더 읽기 쉬운 결과 라벨로 바꾸도록 확장했다. - transcript renderer 분리 2차로 [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)를 추가해 `CreateCompactEventPill`, `AddAgentEventBanner`, `GetDecisionBadgeMeta`를 메인 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 밖으로 옮겼다. 이제 메인 파일은 대화 흐름과 상태 처리에 더 집중하고, 이벤트 배너 렌더는 별도 partial에서 관리한다. +- 업데이트: 2026-04-06 09:36 (KST) + - [OperationalStatusPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/OperationalStatusPresentationCatalog.cs)를 추가해 compact strip/quick strip의 색상, 노출 조건, 빠른 상태 배지 문구 계산을 전용 카탈로그로 분리했다. [AppStateService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AppStateService.cs)의 `GetOperationalStatusPresentation(...)`은 이제 상태 집계 후 카탈로그 결과만 반환한다. + - [PermissionRequestPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs), [ToolResultPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs)에 `Kind`, `Description` 메타를 추가했다. [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)는 이제 이벤트 요약이 비어 있을 때 이 설명을 transcript fallback으로 사용한다. + - [PermissionModePresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionModePresentationCatalog.cs) 에서 제거된 계획 모드 잔재를 걷어내고, [ChatWindow.PermissionPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.PermissionPresentation.cs)의 권한 선택 UI와 상단 배너도 `권한 요청 / 편집 자동 승인 / 권한 건너뛰기 / 읽기 전용`만 다루도록 정리했다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 86cb8e0..a85dc96 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -4912,3 +4912,6 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - Document update: 2026-04-06 08:47 (KST) - This keeps `ChatWindow.xaml.cs` focused on transcript/runtime orchestration and aligns AX Agent more closely with the `claw-code` model where preview/session surfaces are treated as separate presentation layers. - Document update: 2026-04-06 08:55 (KST) - Split file browser presentation out of `ChatWindow.xaml.cs` into `ChatWindow.FileBrowserPresentation.cs`. File browser open/close handlers, folder tree population, file item header/icon/size formatting, context menu actions, and debounced refresh logic now live in a dedicated partial. - Document update: 2026-04-06 08:55 (KST) - This keeps `ChatWindow.xaml.cs` more orchestration-focused and aligns AX Agent more closely with the `claw-code` model where sidebar/file surfaces are separated from transcript and runtime flow. +- Document update: 2026-04-06 09:36 (KST) - Added `OperationalStatusPresentationCatalog.cs` and moved compact-strip / quick-strip styling decisions out of `AppStateService.GetOperationalStatusPresentation(...)`. The service now delegates runtime/status presentation shaping to a dedicated catalog instead of mixing state aggregation with UI color logic. +- Document update: 2026-04-06 09:36 (KST) - Expanded `PermissionRequestPresentationCatalog.cs` and `ToolResultPresentationCatalog.cs` with `Kind` and `Description` metadata so permission/tool-result transcript entries carry typed explanatory text rather than badge-only labels. `ChatWindow.AgentEventRendering.cs` now uses that description as a fallback summary when the event summary is empty. +- Document update: 2026-04-06 09:36 (KST) - Removed the stale `Plan` option from `PermissionModePresentationCatalog.cs` and simplified `ChatWindow.PermissionPresentation.cs` so the permission popup and top-banner presentation only expose the live modes (`권한 요청`, `편집 자동 승인`, `권한 건너뛰기`, `읽기 전용`). diff --git a/docs/claw-code-parity-plan.md b/docs/claw-code-parity-plan.md index 56c05db..84f44ea 100644 --- a/docs/claw-code-parity-plan.md +++ b/docs/claw-code-parity-plan.md @@ -10,6 +10,8 @@ - Updated: 2026-04-05 16:55 (KST) - Current estimated parity vs `claw-code`: core execution engine `82%`, main chat UI `68%`, Cowork/Code status UX `63%`, internal settings linkage `88%`, overall AX Agent `74%`. - Engine-affecting settings should be handled conservatively during parity work. If a setting changes the main execution route, approval flow, or recovery behavior without representing a stable real-world user choice, it should be moved to developer-only UI or removed from user-facing surfaces. +- Updated: 2026-04-06 09:36 (KST) +- Progressed the maintainability track by moving runtime strip styling into `OperationalStatusPresentationCatalog.cs`, expanding permission/tool-result transcript catalogs with typed descriptions, and removing the stale plan-mode presentation branch from permission UI surfaces. The next structural focus remains footer/status/composer presentation slimming and regression ritual enforcement. ## Preserved History (Summary) - Core loop guards and post-tool verification gates are already partially implemented. diff --git a/src/AxCopilot/Services/Agent/OperationalStatusPresentationCatalog.cs b/src/AxCopilot/Services/Agent/OperationalStatusPresentationCatalog.cs new file mode 100644 index 0000000..b2a0b51 --- /dev/null +++ b/src/AxCopilot/Services/Agent/OperationalStatusPresentationCatalog.cs @@ -0,0 +1,68 @@ +using AxCopilot.Services; + +namespace AxCopilot.Services.Agent; + +internal static class OperationalStatusPresentationCatalog +{ + public static AppStateService.OperationalStatusPresentationState Resolve( + AppStateService.OperationalStatusState status, + string tab, + bool hasLiveRuntimeActivity, + int runningConversationCount, + int spotlightConversationCount, + bool runningOnlyFilter, + bool sortConversationsByRecent) + { + var showCompactStrip = !string.Equals(tab, "Chat", StringComparison.OrdinalIgnoreCase) + && (string.Equals(status.StripKind, "permission_waiting", StringComparison.OrdinalIgnoreCase) + || string.Equals(status.StripKind, "failed_run", StringComparison.OrdinalIgnoreCase) + || string.Equals(status.StripKind, "permission_denied", StringComparison.OrdinalIgnoreCase)); + + var (stripBackgroundHex, stripBorderHex, stripForegroundHex) = ResolveStripColors(status.StripKind, showCompactStrip); + + var allowQuickStrip = !string.Equals(tab, "Chat", StringComparison.OrdinalIgnoreCase); + var quickRunningActive = runningOnlyFilter && runningConversationCount > 0; + var quickHotActive = !sortConversationsByRecent && spotlightConversationCount > 0; + var showQuickStrip = allowQuickStrip && (quickRunningActive || quickHotActive); + + return new AppStateService.OperationalStatusPresentationState + { + ShowRuntimeBadge = status.ShowRuntimeBadge && hasLiveRuntimeActivity, + RuntimeLabel = status.RuntimeLabel, + ShowLastCompleted = status.ShowLastCompleted, + LastCompletedText = status.LastCompletedText, + ShowCompactStrip = showCompactStrip, + StripKind = showCompactStrip ? status.StripKind : "none", + StripText = showCompactStrip ? status.StripText : "", + StripBackgroundHex = stripBackgroundHex, + StripBorderHex = stripBorderHex, + StripForegroundHex = stripForegroundHex, + ShowQuickStrip = showQuickStrip, + QuickRunningText = runningConversationCount > 0 ? $"진행 {runningConversationCount}" : "진행", + QuickHotText = spotlightConversationCount > 0 ? $"활동 {spotlightConversationCount}" : "활동", + QuickRunningActive = quickRunningActive, + QuickHotActive = quickHotActive, + QuickRunningBackgroundHex = quickRunningActive ? "#DBEAFE" : "#F8FAFC", + QuickRunningBorderHex = quickRunningActive ? "#93C5FD" : "#E5E7EB", + QuickRunningForegroundHex = quickRunningActive ? "#1D4ED8" : "#6B7280", + QuickHotBackgroundHex = quickHotActive ? "#F5F3FF" : "#F8FAFC", + QuickHotBorderHex = quickHotActive ? "#C4B5FD" : "#E5E7EB", + QuickHotForegroundHex = quickHotActive ? "#6D28D9" : "#6B7280", + }; + } + + private static (string backgroundHex, string borderHex, string foregroundHex) ResolveStripColors(string stripKind, bool visible) + { + if (!visible) + return ("", "", ""); + + if (string.Equals(stripKind, "permission_waiting", StringComparison.OrdinalIgnoreCase)) + return ("#FFF7ED", "#FDBA74", "#C2410C"); + + if (string.Equals(stripKind, "failed_run", StringComparison.OrdinalIgnoreCase) + || string.Equals(stripKind, "permission_denied", StringComparison.OrdinalIgnoreCase)) + return ("#FEF2F2", "#FECACA", "#991B1B"); + + return ("", "", ""); + } +} diff --git a/src/AxCopilot/Services/Agent/PermissionModePresentationCatalog.cs b/src/AxCopilot/Services/Agent/PermissionModePresentationCatalog.cs index e12ad81..0ab663e 100644 --- a/src/AxCopilot/Services/Agent/PermissionModePresentationCatalog.cs +++ b/src/AxCopilot/Services/Agent/PermissionModePresentationCatalog.cs @@ -1,4 +1,4 @@ -namespace AxCopilot.Services.Agent; +namespace AxCopilot.Services.Agent; internal sealed record PermissionModePresentation( string Mode, @@ -23,12 +23,6 @@ internal static class PermissionModePresentationCatalog "편집 자동 승인", "모든 파일 편집을 자동 승인합니다.", "#107C10"), - new PermissionModePresentation( - PermissionModeCatalog.Plan, - "\uE7C3", - "계획 모드", - "변경하기 전에 계획을 먼저 만듭니다.", - "#4338CA"), new PermissionModePresentation( PermissionModeCatalog.BypassPermissions, "\uE814", @@ -41,7 +35,7 @@ internal static class PermissionModePresentationCatalog { var normalized = PermissionModeCatalog.NormalizeGlobalMode(mode); return Ordered.FirstOrDefault(item => - string.Equals(item.Mode, normalized, StringComparison.OrdinalIgnoreCase)) - ?? Ordered[0]; + string.Equals(item.Mode, normalized, StringComparison.OrdinalIgnoreCase)) + ?? Ordered[0]; } } diff --git a/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs b/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs index 57edc5a..e2e609e 100644 --- a/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs +++ b/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs @@ -1,10 +1,10 @@ -using System; - namespace AxCopilot.Services.Agent; internal sealed record PermissionRequestPresentation( + string Kind, string Icon, string Label, + string Description, string BackgroundHex, string ForegroundHex); @@ -15,49 +15,106 @@ internal static class PermissionRequestPresentationCatalog var tool = (toolName ?? string.Empty).Trim().ToLowerInvariant(); if (tool.Contains("bash") || tool.Contains("powershell") || tool.Contains("process")) - { - return pending - ? new PermissionRequestPresentation("\uE756", "명령 실행 권한 요청", "#FEF2F2", "#DC2626") - : new PermissionRequestPresentation("\uE73E", "명령 실행 권한 승인", "#ECFDF5", "#059669"); - } + return Build( + "command", + pending, + "\uE756", + "명령 실행 권한 요청", + "명령 실행 권한 확인", + "터미널 명령을 실행하기 전에 확인이 필요합니다.", + "명령 실행이 승인되어 계속 진행합니다.", + "#FEF2F2", + "#DC2626"); if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http")) - { - return pending - ? new PermissionRequestPresentation("\uE774", "웹 요청 권한 요청", "#FFF7ED", "#C2410C") - : new PermissionRequestPresentation("\uE73E", "웹 요청 권한 승인", "#ECFDF5", "#059669"); - } + return Build( + "web", + pending, + "\uE774", + "웹 요청 권한 요청", + "웹 요청 권한 확인", + "외부 웹 요청 전 사용자 확인이 필요합니다.", + "웹 요청이 승인되어 계속 진행합니다.", + "#FFF7ED", + "#C2410C"); if (tool.Contains("skill")) - { - return pending - ? new PermissionRequestPresentation("\uE8A5", "스킬 실행 권한 요청", "#F5F3FF", "#7C3AED") - : new PermissionRequestPresentation("\uE73E", "스킬 실행 권한 승인", "#ECFDF5", "#059669"); - } + return Build( + "skill", + pending, + "\uE8A5", + "스킬 실행 권한 요청", + "스킬 실행 권한 확인", + "연결된 스킬 실행 전 확인이 필요합니다.", + "스킬 실행이 승인되어 계속 진행합니다.", + "#F5F3FF", + "#7C3AED"); if (tool.Contains("ask")) - { - return pending - ? new PermissionRequestPresentation("\uE897", "의견 요청 권한 확인", "#EFF6FF", "#2563EB") - : new PermissionRequestPresentation("\uE73E", "의견 요청 권한 승인", "#ECFDF5", "#059669"); - } + return Build( + "question", + pending, + "\uE897", + "의견 요청 확인", + "의견 요청 완료", + "사용자에게 선택이나 답변을 요청합니다.", + "사용자 의견을 받아 다음 단계로 진행합니다.", + "#EFF6FF", + "#2563EB"); if (tool.Contains("file_edit") || tool.Contains("file_write") || tool.Contains("edit")) - { - return pending - ? new PermissionRequestPresentation("\uE70F", "파일 수정 권한 요청", "#FFF7ED", "#C2410C") - : new PermissionRequestPresentation("\uE73E", "파일 수정 권한 승인", "#ECFDF5", "#059669"); - } + return Build( + "file_edit", + pending, + "\uE70F", + "파일 수정 권한 요청", + "파일 수정 권한 확인", + "파일을 만들거나 수정하기 전에 확인이 필요합니다.", + "파일 수정이 승인되어 계속 진행합니다.", + "#FFF7ED", + "#C2410C"); if (tool.Contains("file") || tool.Contains("glob") || tool.Contains("grep") || tool.Contains("folder")) - { - return pending - ? new PermissionRequestPresentation("\uE8A5", "파일 접근 권한 요청", "#FFF7ED", "#C2410C") - : new PermissionRequestPresentation("\uE73E", "파일 접근 권한 승인", "#ECFDF5", "#059669"); - } + return Build( + "file_access", + pending, + "\uE8A5", + "파일 접근 권한 요청", + "파일 접근 권한 확인", + "폴더와 파일 내용을 읽기 전에 확인이 필요합니다.", + "파일 접근이 승인되어 계속 진행합니다.", + "#FFF7ED", + "#C2410C"); - return pending - ? new PermissionRequestPresentation("\uE897", "권한 요청", "#FFF7ED", "#C2410C") - : new PermissionRequestPresentation("\uE73E", "권한 승인", "#ECFDF5", "#059669"); + return Build( + "generic", + pending, + "\uE897", + "권한 요청", + "권한 확인", + "계속 진행하기 전에 사용자 확인이 필요합니다.", + "요청이 승인되어 계속 진행합니다.", + "#FFF7ED", + "#C2410C"); + } + + private static PermissionRequestPresentation Build( + string kind, + bool pending, + string icon, + string pendingLabel, + string resolvedLabel, + string pendingDescription, + string resolvedDescription, + string backgroundHex, + string foregroundHex) + { + return new PermissionRequestPresentation( + kind, + pending ? icon : "\uE73E", + pending ? pendingLabel : resolvedLabel, + pending ? pendingDescription : resolvedDescription, + pending ? backgroundHex : "#ECFDF5", + pending ? foregroundHex : "#059669"); } } diff --git a/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs b/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs index 64f7173..8307d41 100644 --- a/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs +++ b/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs @@ -1,10 +1,10 @@ -using System; - namespace AxCopilot.Services.Agent; internal sealed record ToolResultPresentation( + string Kind, string Icon, string Label, + string Description, string BackgroundHex, string ForegroundHex, string StatusKind); @@ -16,76 +16,134 @@ internal static class ToolResultPresentationCatalog var summary = (evt.Summary ?? string.Empty).Trim(); var tool = (evt.ToolName ?? string.Empty).Trim().ToLowerInvariant(); var baseLabel = string.IsNullOrWhiteSpace(fallbackLabel) ? "도구 결과" : fallbackLabel; + var kind = ResolveKind(tool); if (summary.Contains("취소", StringComparison.OrdinalIgnoreCase) || summary.Contains("중단", StringComparison.OrdinalIgnoreCase) || evt.Type == AgentEventType.StopRequested) { - return new ToolResultPresentation("\uE711", $"{baseLabel} 취소", "#F8FAFC", "#475569", "cancel"); + return new ToolResultPresentation( + "cancel", + "\uE711", + $"{baseLabel} 취소", + "요청이 중단되어 결과가 취소되었습니다.", + "#F8FAFC", + "#475569", + "cancel"); } if (summary.Contains("거부", StringComparison.OrdinalIgnoreCase) || summary.Contains("반려", StringComparison.OrdinalIgnoreCase) || summary.Contains("권한 거부", StringComparison.OrdinalIgnoreCase)) { - return new ToolResultPresentation("\uE783", $"{baseLabel} 거부", "#FEF2F2", "#DC2626", "reject"); + return new ToolResultPresentation( + "reject", + "\uE783", + $"{baseLabel} 거부", + "권한이 거부되어 작업이 중단되었습니다.", + "#FEF2F2", + "#DC2626", + "reject"); } if (!evt.Success || evt.Type == AgentEventType.Error) { return new ToolResultPresentation( + kind, "\uE783", - BuildFailureLabel(tool, baseLabel), + BuildFailureLabel(kind, baseLabel), + BuildFailureDescription(kind), "#FEF2F2", "#DC2626", "error"); } return new ToolResultPresentation( + kind, "\uE73E", - BuildSuccessLabel(tool, baseLabel), + BuildSuccessLabel(kind, baseLabel), + BuildSuccessDescription(kind), "#ECFDF5", "#16A34A", "success"); } - private static string BuildSuccessLabel(string tool, string baseLabel) + private static string ResolveKind(string tool) { if (tool.Contains("file")) - return "파일 작업 완료"; + return "file"; if (tool.Contains("build") || tool.Contains("test")) - return "빌드/테스트 완료"; + return "build"; if (tool.Contains("git") || tool.Contains("diff")) - return "Git 작업 완료"; + return "git"; if (tool.Contains("document") || tool.Contains("format") || tool.Contains("template")) - return "문서 작업 완료"; + return "document"; if (tool.Contains("skill")) - return "스킬 실행 완료"; + return "skill"; if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http")) - return "웹 요청 완료"; + return "web"; if (tool.Contains("process") || tool.Contains("bash") || tool.Contains("powershell")) - return "명령 실행 완료"; - - return baseLabel; + return "command"; + return "generic"; } - private static string BuildFailureLabel(string tool, string baseLabel) + private static string BuildSuccessLabel(string kind, string baseLabel) { - if (tool.Contains("file")) - return "파일 작업 실패"; - if (tool.Contains("build") || tool.Contains("test")) - return "빌드/테스트 실패"; - if (tool.Contains("git") || tool.Contains("diff")) - return "Git 작업 실패"; - if (tool.Contains("document") || tool.Contains("format") || tool.Contains("template")) - return "문서 작업 실패"; - if (tool.Contains("skill")) - return "스킬 실행 실패"; - if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http")) - return "웹 요청 실패"; - if (tool.Contains("process") || tool.Contains("bash") || tool.Contains("powershell")) - return "명령 실행 실패"; + return kind switch + { + "file" => "파일 작업 완료", + "build" => "빌드/테스트 완료", + "git" => "Git 작업 완료", + "document" => "문서 작업 완료", + "skill" => "스킬 실행 완료", + "web" => "웹 요청 완료", + "command" => "명령 실행 완료", + _ => baseLabel, + }; + } - return $"{baseLabel} 실패"; + private static string BuildFailureLabel(string kind, string baseLabel) + { + return kind switch + { + "file" => "파일 작업 실패", + "build" => "빌드/테스트 실패", + "git" => "Git 작업 실패", + "document" => "문서 작업 실패", + "skill" => "스킬 실행 실패", + "web" => "웹 요청 실패", + "command" => "명령 실행 실패", + _ => $"{baseLabel} 실패", + }; + } + + private static string BuildSuccessDescription(string kind) + { + return kind switch + { + "file" => "파일 처리 결과가 정상적으로 반영되었습니다.", + "build" => "빌드나 테스트 단계가 성공적으로 끝났습니다.", + "git" => "Git 관련 작업이 정상적으로 완료되었습니다.", + "document" => "문서 생성 또는 변환 작업이 완료되었습니다.", + "skill" => "선택된 스킬이 정상적으로 실행되었습니다.", + "web" => "웹 요청이 정상적으로 완료되었습니다.", + "command" => "명령 실행이 정상적으로 끝났습니다.", + _ => "요청한 작업이 정상적으로 완료되었습니다.", + }; + } + + private static string BuildFailureDescription(string kind) + { + return kind switch + { + "file" => "파일 처리 중 문제가 발생했습니다.", + "build" => "빌드나 테스트 단계에서 실패가 발생했습니다.", + "git" => "Git 관련 작업이 실패했습니다.", + "document" => "문서 생성 또는 변환 작업이 실패했습니다.", + "skill" => "스킬 실행 중 문제가 발생했습니다.", + "web" => "웹 요청 처리에 실패했습니다.", + "command" => "명령 실행 중 오류가 발생했습니다.", + _ => "작업 처리 중 오류가 발생했습니다.", + }; } } diff --git a/src/AxCopilot/Services/AppStateService.cs b/src/AxCopilot/Services/AppStateService.cs index d20028b..4940e19 100644 --- a/src/AxCopilot/Services/AppStateService.cs +++ b/src/AxCopilot/Services/AppStateService.cs @@ -654,60 +654,14 @@ public sealed class AppStateService bool sortConversationsByRecent) { var status = GetOperationalStatus(tab); - var showCompactStrip = !string.Equals(tab, "Chat", StringComparison.OrdinalIgnoreCase) - && (string.Equals(status.StripKind, "permission_waiting", StringComparison.OrdinalIgnoreCase) - || string.Equals(status.StripKind, "failed_run", StringComparison.OrdinalIgnoreCase) - || string.Equals(status.StripKind, "permission_denied", StringComparison.OrdinalIgnoreCase)); - - var stripBackgroundHex = ""; - var stripBorderHex = ""; - var stripForegroundHex = ""; - if (showCompactStrip) - { - if (string.Equals(status.StripKind, "permission_waiting", StringComparison.OrdinalIgnoreCase)) - { - stripBackgroundHex = "#FFF7ED"; - stripBorderHex = "#FDBA74"; - stripForegroundHex = "#C2410C"; - } - else if (string.Equals(status.StripKind, "failed_run", StringComparison.OrdinalIgnoreCase) - || string.Equals(status.StripKind, "permission_denied", StringComparison.OrdinalIgnoreCase)) - { - stripBackgroundHex = "#FEF2F2"; - stripBorderHex = "#FECACA"; - stripForegroundHex = "#991B1B"; - } - } - - var allowQuickStrip = !string.Equals(tab, "Chat", StringComparison.OrdinalIgnoreCase); - var quickRunningActive = runningOnlyFilter && runningConversationCount > 0; - var quickHotActive = !sortConversationsByRecent && spotlightConversationCount > 0; - var showQuickStrip = allowQuickStrip && (quickRunningActive || quickHotActive); - - return new OperationalStatusPresentationState - { - ShowRuntimeBadge = status.ShowRuntimeBadge && hasLiveRuntimeActivity, - RuntimeLabel = status.RuntimeLabel, - ShowLastCompleted = status.ShowLastCompleted, - LastCompletedText = status.LastCompletedText, - ShowCompactStrip = showCompactStrip, - StripKind = showCompactStrip ? status.StripKind : "none", - StripText = showCompactStrip ? status.StripText : "", - StripBackgroundHex = stripBackgroundHex, - StripBorderHex = stripBorderHex, - StripForegroundHex = stripForegroundHex, - ShowQuickStrip = showQuickStrip, - QuickRunningText = runningConversationCount > 0 ? $"진행 {runningConversationCount}" : "진행", - QuickHotText = spotlightConversationCount > 0 ? $"활동 {spotlightConversationCount}" : "활동", - QuickRunningActive = quickRunningActive, - QuickHotActive = quickHotActive, - QuickRunningBackgroundHex = quickRunningActive ? "#DBEAFE" : "#F8FAFC", - QuickRunningBorderHex = quickRunningActive ? "#93C5FD" : "#E5E7EB", - QuickRunningForegroundHex = quickRunningActive ? "#1D4ED8" : "#6B7280", - QuickHotBackgroundHex = quickHotActive ? "#F5F3FF" : "#F8FAFC", - QuickHotBorderHex = quickHotActive ? "#C4B5FD" : "#E5E7EB", - QuickHotForegroundHex = quickHotActive ? "#6D28D9" : "#6B7280", - }; + return OperationalStatusPresentationCatalog.Resolve( + status, + tab, + hasLiveRuntimeActivity, + runningConversationCount, + spotlightConversationCount, + runningOnlyFilter, + sortConversationsByRecent); } public IReadOnlyList GetDraftQueueItems(string tab) diff --git a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs index d764ffc..a0daf55 100644 --- a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs +++ b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs @@ -134,6 +134,15 @@ public partial class ChatWindow ? GetAgentItemDisplayName(evt.ToolName, slashPrefix: true) : GetAgentItemDisplayName(evt.ToolName); var eventSummaryText = BuildAgentEventSummaryText(evt, itemDisplayName); + if (string.IsNullOrWhiteSpace(eventSummaryText)) + { + eventSummaryText = evt.Type switch + { + AgentEventType.PermissionRequest or AgentEventType.PermissionGranted => permissionPresentation?.Description ?? "", + AgentEventType.ToolResult => toolResultPresentation?.Description ?? "", + _ => "" + }; + } var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray; diff --git a/src/AxCopilot/Views/ChatWindow.PermissionPresentation.cs b/src/AxCopilot/Views/ChatWindow.PermissionPresentation.cs index 3076644..ec1ad36 100644 --- a/src/AxCopilot/Views/ChatWindow.PermissionPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.PermissionPresentation.cs @@ -21,9 +21,7 @@ public partial class ChatWindow ChatConversation? currentConversation; lock (_convLock) currentConversation = _currentConversation; - var coreLevels = PermissionModePresentationCatalog.Ordered - .Where(item => !string.Equals(item.Mode, PermissionModeCatalog.Plan, StringComparison.OrdinalIgnoreCase)) - .ToList(); + var coreLevels = PermissionModePresentationCatalog.Ordered.ToList(); var current = PermissionModeCatalog.NormalizeGlobalMode(_settings.Settings.Llm.FilePermission); void AddPermissionRows(Panel container, IEnumerable levels) @@ -210,7 +208,6 @@ public partial class ChatWindow PermissionIcon.Text = perm switch { "AcceptEdits" => "\uE73E", - "Plan" => "\uE7C3", "BypassPermissions" => "\uE7BA", "Deny" => "\uE711", _ => "\uE8D7", @@ -218,7 +215,7 @@ public partial class ChatWindow if (BtnPermission != null) { var operationMode = OperationModePolicy.Normalize(_settings.Settings.OperationMode); - BtnPermission.ToolTip = $"{summary.Description}\n운영 모드: {operationMode}\n기본값 {PermissionModeCatalog.ToDisplayLabel(summary.DefaultMode)} · 예외 {summary.OverrideCount}개"; + BtnPermission.ToolTip = $"{summary.Description}\n운영 모드: {operationMode}\n기본값: {PermissionModeCatalog.ToDisplayLabel(summary.DefaultMode)} · 예외 {summary.OverrideCount}개"; BtnPermission.Background = Brushes.Transparent; BtnPermission.BorderThickness = new Thickness(1); } @@ -240,7 +237,7 @@ public partial class ChatWindow PermissionTopBannerIcon.Foreground = activeColor; PermissionTopBannerTitle.Text = "현재 권한 모드 · 편집 자동 승인"; PermissionTopBannerTitle.Foreground = BrushFromHex("#166534"); - PermissionTopBannerText.Text = "모든 파일 편집을 자동 승인합니다. 명령 실행은 계속 확인합니다."; + PermissionTopBannerText.Text = "모든 파일 편집은 자동 승인하고, 명령 실행만 계속 확인합니다."; PermissionTopBanner.Visibility = Visibility.Collapsed; } } @@ -258,7 +255,7 @@ public partial class ChatWindow PermissionTopBannerIcon.Foreground = denyColor; PermissionTopBannerTitle.Text = "현재 권한 모드 · 읽기 전용"; PermissionTopBannerTitle.Foreground = denyColor; - PermissionTopBannerText.Text = "파일 읽기만 허용하고 생성/수정/삭제는 차단합니다."; + PermissionTopBannerText.Text = "파일 읽기만 허용하고 생성, 수정, 삭제는 차단합니다."; PermissionTopBanner.Visibility = Visibility.Collapsed; } } @@ -283,30 +280,14 @@ public partial class ChatWindow else { var defaultFg = BrushFromHex("#2563EB"); - var iconFg = perm switch - { - "Plan" => new SolidColorBrush(Color.FromRgb(0x43, 0x38, 0xCA)), - _ => new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xEB)), - }; + var iconFg = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xEB)); PermissionLabel.Foreground = defaultFg; PermissionIcon.Foreground = iconFg; if (BtnPermission != null) - BtnPermission.BorderBrush = perm == PermissionModeCatalog.Plan - ? BrushFromHex("#C7D2FE") - : BrushFromHex("#BFDBFE"); + BtnPermission.BorderBrush = BrushFromHex("#BFDBFE"); if (PermissionTopBanner != null) { - if (perm == PermissionModeCatalog.Plan) - { - PermissionTopBanner.BorderBrush = BrushFromHex("#C7D2FE"); - PermissionTopBannerIcon.Text = "\uE7C3"; - PermissionTopBannerIcon.Foreground = BrushFromHex("#4338CA"); - PermissionTopBannerTitle.Text = "현재 권한 모드 · 계획 모드"; - PermissionTopBannerTitle.Foreground = BrushFromHex("#4338CA"); - PermissionTopBannerText.Text = "변경 전에 계획을 먼저 만들고 승인 흐름을 우선합니다."; - PermissionTopBanner.Visibility = Visibility.Collapsed; - } - else if (perm == PermissionModeCatalog.Default) + if (perm == PermissionModeCatalog.Default) { PermissionTopBanner.BorderBrush = BrushFromHex("#BFDBFE"); PermissionTopBannerIcon.Text = "\uE8D7";