From 227f5ab0d3142a015f3ff762fcfa9dae1f4f6d89 Mon Sep 17 00:00:00 2001 From: lacvet Date: Thu, 9 Apr 2026 14:49:53 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=90=EC=9D=B4=EC=A0=84=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=ED=91=9C=EC=8B=9C=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EB=A5=BC=20claude-code=EC=8B=9D=20row=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9E=AC=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AgentTranscriptDisplayCatalog를 row presentation 중심으로 재구성해 thinking/waiting/compact/tool activity/permission/tool result/status를 타입별로 분리함 - PermissionRequestPresentationCatalog와 ToolResultPresentationCatalog를 정리해 권한 요청과 결과 상태를 행위/상태 기준으로 더 명확하게 표현함 - ChatWindow.AgentEventRendering에서 process feed 계열 이벤트를 GroupKey 기준으로 병합해 append 수를 줄이고 진행 흐름이 기본 transcript에 남도록 조정함 - FooterPresentation에서 Cowork/Chat 프리셋 안내 카드가 execution event 이후 자동으로 숨겨지도록 하고 입력 워터마크와 footer 기본 문구를 정리함 - render_messages 성능 로그에 processFeed append/merge 수치와 rowKindCounts를 추가해 %APPDATA%\\AxCopilot\\perf 기준 실검증이 가능하도록 함 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 기준 경고 0 / 오류 0 확인 --- README.md | 11 + docs/DEVELOPMENT.md | 696 +++++++++--------- .../Agent/AgentTranscriptDisplayCatalog.cs | 300 +++++++- .../PermissionRequestPresentationCatalog.cs | 80 +- .../Agent/ToolResultPresentationCatalog.cs | 190 +++-- .../Views/ChatWindow.AgentEventRendering.cs | 89 ++- .../Views/ChatWindow.FooterPresentation.cs | 38 +- .../Views/ChatWindow.TranscriptHost.cs | 5 + .../Views/ChatWindow.TranscriptRendering.cs | 6 + 9 files changed, 867 insertions(+), 548 deletions(-) diff --git a/README.md b/README.md index 0b3b585..71638a3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-09 13:05 (KST) +- AX Agent transcript 표시를 `claude-code`식 row 타입 기반으로 재정리했습니다. `thinking / waiting / compact / tool activity / permission / tool result / status`를 별도 row 의미로 정규화하고, process feed는 같은 활동 그룹이면 이어붙여 append 수를 줄이도록 바꿨습니다. +- 권한 요청과 도구 결과 카탈로그를 다시 정리해 Cowork/Code 진행 중 무엇을 하는지, 어떤 결과 상태인지가 기본 transcript에서 더 읽히도록 맞췄습니다. 동시에 render 성능 로그에 row kind 분포와 process feed append/merge 수치를 남겨 이후 실검증 때 구조 개선 효과를 바로 판단할 수 있게 했습니다. +- Cowork/Chat 하단 프리셋 안내 카드는 실제 메시지뿐 아니라 execution event가 생긴 뒤에도 자동으로 숨겨 결과/진행 화면을 가리지 않도록 조정했고, 입력 워터마크와 footer 기본 문구도 정상 한국어 기준으로 다시 정리했습니다. + - 업데이트: 2026-04-09 01:33 (KST) - AX Agent transcript 호스트를 `ObservableCollection` 직접 주입 구조에서 `TranscriptVisualItem + TranscriptVisualHost` 기반의 지연 materialization 구조로 올렸습니다. `MessageList`는 virtualization 설정을 유지한 채 필요한 시점에만 실제 버블 UI를 생성할 수 있는 기반을 갖습니다. - `StreamingToolExecutionCoordinator`를 `IToolExecutionCoordinator` 인터페이스 뒤로 숨겨 Cowork/Code tool streaming executor를 루프 구현에서 구조적으로 분리했습니다. 이후 executor 교체, 테스트 더블 주입, 모델별 executor 분기를 더 쉽게 할 수 있는 상태입니다. @@ -1548,3 +1553,9 @@ MIT License - [MultiReadTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MultiReadTool.cs)는 한 번에 읽을 수 있는 최대 파일 수를 20개에서 8개로 낮춰 초기 과탐색 토큰 낭비를 줄이도록 했습니다. - [AgentLoopExplorationPolicy.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopExplorationPolicy.cs)와 [AgentLoopService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentLoopService.cs)에 탐색 범위 분류기와 broad-scan corrective hint를 추가해, 좁은 질문에서 반복적인 `folder_map`/대량 `multi_read`가 나오면 관련 파일만 다시 고르도록 교정합니다. - [AgentPerformanceLogService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentPerformanceLogService.cs)는 `%APPDATA%\\AxCopilot\\perf`에 `exploration_breadth` 로그를 남겨 `folder_map` 호출 수, 총 읽은 파일 수, broad scan 여부를 실사용 기준으로 확인할 수 있게 했습니다. +- 업데이트: 2026-04-09 11:12 (KST) + - `claude-code`의 `Messages.tsx`, `MessageRow.tsx`, `GroupedToolUseContent.tsx`, `UserToolResultMessage`, `PermissionRequest` 구조를 다시 대조해 AX transcript 표시 계약을 row 중심으로 정리했습니다. + - [AgentTranscriptDisplayCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs)에 `TranscriptRowKind`와 `AgentTranscriptRowPresentation`을 도입해 thinking, waiting, compact, tool activity, permission, tool result, status를 한 번에 정규화하도록 바꿨습니다. + - [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)는 이 row 메타를 사용해 고빈도 process feed를 같은 group key 기준으로 교체 렌더하도록 바꿨고, 연속 읽기/검색/단계 이벤트를 더 적은 row 수로 보여주게 했습니다. + - [ToolResultPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs)와 [PermissionRequestPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs)를 정리해 실패/거부/취소/승인 필요와 권한 행위를 유형별 카드 메타로 다시 맞췄습니다. + - [ChatWindow.TranscriptHost.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.TranscriptHost.cs), [ChatWindow.TranscriptRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs)는 grouped process feed append/merge 수를 추적하도록 바꿔, `claude-code`식 activity grouping이 실제 렌더 수를 얼마나 줄였는지 성능 로그로 확인할 수 있게 했습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 402e27d..826a905 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,506 +1,474 @@ -# AX Copilot - 개발 문서 +# AX Copilot - 媛쒕컻 臾몄꽌 -> 최종 업데이트: 2026-04-09 · 버전 0.7.3 + +## claude-code식 transcript 표시 구조 정리 + +- 업데이트: 2026-04-09 13:05 (KST) +- AgentTranscriptDisplayCatalog를 row presentation 중심으로 재구성해 thinking / waiting / compact / tool activity / permission / tool result / status를 개별 transcript row 의미로 다룰 수 있게 정리했습니다. +- ChatWindow.AgentEventRendering은 process feed 계열 이벤트를 같은 GroupKey 단위로 병합해, 긴 Cowork/Code 실행 중 append 수를 줄이면서도 주요 활동 흐름이 기본 transcript에 남도록 조정했습니다. +- PermissionRequestPresentationCatalog, ToolResultPresentationCatalog를 다시 정리해 권한 요청과 도구 결과를 행위/상태 기준으로 구분하고, transcript 렌더와 popup이 같은 메타를 공유하도록 맞췄습니다. +- ChatWindow.FooterPresentation은 execution event가 생긴 뒤에는 프리셋 안내 카드를 자동으로 숨기도록 바꿔 결과/진행 화면을 덮지 않게 했고, Cowork/Code 입력 워터마크와 footer 기본 문구도 정상 한국어 기준으로 다시 정리했습니다. +- render_messages 성능 로그에는 processFeedAppends, processFeedMerges, rowKindCounts를 함께 남겨 %APPDATA%\\AxCopilot\\perf 기준으로 transcript grouping 효과를 수치로 비교할 수 있게 했습니다. + +## 1. ?꾨줈?앺듃 媛쒖슂 + +AX Copilot?€ Windows???앹궛???곗쿂 + AI ?먯씠?꾪듃 ?곗뒪?ы넲 ?깆엯?덈떎. +- **?곗쿂**: Alfred/Raycast ?ㅽ??쇱쓽 ?쇱? 寃€?? 紐낅졊 ?ㅽ뻾, ?꾩젽 +- **?먯씠?꾪듃**: LLM 湲곕컲 ?€?뷀삎 肄붾뱶/臾몄꽌 ?묒뾽 ?먮룞??(?꾧뎄 ?몄텧 猷⑦봽) +- **??諛?*: ?쒖뒪??由ъ냼?? ?대┰蹂대뱶, ?ㅽ겕由곗꺑 ??鍮좊Ⅸ ?묎렐 --- -## 1. 프로젝트 개요 +## 2. 湲곗닠 ?ㅽ깮 -AX Copilot은 Windows용 생산성 런처 + AI 에이전트 데스크톱 앱입니다. -- **런처**: Alfred/Raycast 스타일의 퍼지 검색, 명령 실행, 위젯 -- **에이전트**: LLM 기반 대화형 코드/문서 작업 자동화 (도구 호출 루프) -- **독 바**: 시스템 리소스, 클립보드, 스크린샷 등 빠른 접근 - ---- - -## 2. 기술 스택 - -| 항목 | 값 | +| ??ぉ | 媛?| |------|-----| -| 프레임워크 | .NET 8 (net8.0-windows10.0.17763.0) | -| UI | WPF + Windows Forms (하이브리드) | -| 언어 | C# 12 | -| 패턴 | MVVM, 이벤트 기반, 싱글톤 서비스 | -| 테스트 | xUnit 2.9 + FluentAssertions 6.12 | -| 빌드 | dotnet CLI, PublishSingleFile | +| ?꾨젅?꾩썙??| .NET 8 (net8.0-windows10.0.17763.0) | +| UI | WPF + Windows Forms (?섏씠釉뚮━?? | +| ?몄뼱 | C# 12 | +| ?⑦꽩 | MVVM, ?대깽??湲곕컲, ?깃????쒕퉬??| +| ?뚯뒪??| xUnit 2.9 + FluentAssertions 6.12 | +| 鍮뚮뱶 | dotnet CLI, PublishSingleFile | -### 주요 NuGet 패키지 +### 二쇱슂 NuGet ?⑦궎吏€ -| 패키지 | 용도 | +| ?⑦궎吏€ | ?⑸룄 | |--------|------| -| DocumentFormat.OpenXml 3.2.0 | DOCX/XLSX/PPTX 생성 | -| Markdig 0.37.0 | Markdown → HTML 렌더링 | -| Microsoft.Data.Sqlite 8.0 | SQLite (대화 저장소) | -| Microsoft.Web.WebView2 | HTML 미리보기, 가이드 뷰어 | -| QRCoder 1.6.0 | QR 코드 생성 | -| System.Security.Cryptography.ProtectedData | DPAPI 암호화 | -| UglyToad.PdfPig | PDF 읽기 | +| DocumentFormat.OpenXml 3.2.0 | DOCX/XLSX/PPTX ?앹꽦 | +| Markdig 0.37.0 | Markdown ??HTML ?뚮뜑留?| +| Microsoft.Data.Sqlite 8.0 | SQLite (?€???€?μ냼) | +| Microsoft.Web.WebView2 | HTML 誘몃━蹂닿린, 媛€?대뱶 酉곗뼱 | +| QRCoder 1.6.0 | QR 肄붾뱶 ?앹꽦 | +| System.Security.Cryptography.ProtectedData | DPAPI ?뷀샇??| +| UglyToad.PdfPig | PDF ?쎄린 | --- -## 3. 솔루션 구조 +## 3. ?붾(??援ъ“ ``` src/ -├── AxCopilot/ # 메인 WPF 앱 (v0.7.3) -│ ├── Assets/ # 아이콘, 프리셋 JSON, 암호화된 가이드, 마스코트 -│ ├── Core/ # FuzzyEngine, CommandResolver, InputListener, PluginHost -│ ├── Handlers/ # 136개 빌트인 명령 핸들러 -│ ├── Models/ # AppSettings, ChatModels, McpSettings -│ ├── Security/ # AntiTamper (디버거/디컴파일러 탐지) -│ ├── Services/ # 60개 서비스 -│ │ └── Agent/ # 에이전트 루프 + 114개 도구 -│ ├── Themes/ # 9개 테마 (Dark, Light, OLED, Nord, Monokai 등) -│ ├── ViewModels/ # LauncherViewModel, SettingsViewModel, StatisticsViewModel -│ └── Views/ # 30개 XAML 윈도우 -├── AxCopilot.SDK/ # 플러그인 SDK (IActionHandler 인터페이스) -├── AxCopilot.Installer/ # Windows Forms 설치 프로그램 (.NET Framework 4.8) -├── AxCopilot.Tests/ # xUnit 단위/통합 테스트 -└── AxKeyEncryptor/ # API 키 DPAPI 암호화 유틸리티 +?쒋??€ AxCopilot/ # 硫붿씤 WPF ??(v0.7.3) +?? ?쒋??€ Assets/ # ?꾩씠肄? ?꾨━??JSON, ?뷀샇?붾맂 媛€?대뱶, 留덉뒪肄뷀듃 +?? ?쒋??€ Core/ # FuzzyEngine, CommandResolver, InputListener, PluginHost +?? ?쒋??€ Handlers/ # 136媛?鍮뚰듃??紐낅졊 ?몃뱾???? ?쒋??€ Models/ # AppSettings, ChatModels, McpSettings +?? ?쒋??€ Security/ # AntiTamper (?붾쾭嫄??붿뺨?뚯씪???먯?) +?? ?쒋??€ Services/ # 60媛??쒕퉬???? ?? ?붴??€ Agent/ # ?먯씠?꾪듃 猷⑦봽 + 114媛??꾧뎄 +?? ?쒋??€ Themes/ # 9媛??뚮쭏 (Dark, Light, OLED, Nord, Monokai ?? +?? ?쒋??€ ViewModels/ # LauncherViewModel, SettingsViewModel, StatisticsViewModel +?? ?붴??€ Views/ # 30媛?XAML ?덈룄???쒋??€ AxCopilot.SDK/ # ?뚮윭洹몄씤 SDK (IActionHandler ?명꽣?섏씠?? +?쒋??€ AxCopilot.Installer/ # Windows Forms ?ㅼ튂 ?꾨줈洹몃옩 (.NET Framework 4.8) +?쒋??€ AxCopilot.Tests/ # xUnit ?⑥쐞/?듯빀 ?뚯뒪???붴??€ AxKeyEncryptor/ # API ??DPAPI ?뷀샇???좏떥由ы떚 ``` --- -## 4. 앱 시작 흐름 (App.xaml.cs) +## 4. ???쒖옉 ?먮쫫 (App.xaml.cs) ``` OnStartup() - ├─ AntiTamper 디버거 감지 (Release 빌드) - ├─ 단일 인스턴스 뮤텍스 확인 - ├─ SettingsService 초기화 + 설정 로드 - ├─ ChatStorageService 보관 정책 실행 (만료 대화 정리) - ├─ L10n 언어 초기화 - ├─ 서비스 초기화 - │ ├─ AgentMemoryService - │ ├─ ChatSessionStateService - │ ├─ AppStateService - │ ├─ IndexService (백그라운드 파일 인덱싱) - │ ├─ FuzzyEngine + CommandResolver - │ ├─ ContextManager - │ ├─ SessionTrackingService - │ ├─ WorktimeReminderService - │ └─ ClipboardHistoryService - ├─ 빌트인 핸들러 등록 (136개) - ├─ SchedulerService + PluginHost 초기화 - ├─ InputListener 시작 (글로벌 핫키) - └─ 런처/설정/트레이 윈도우 생성 + ?쒋? AntiTamper ?붾쾭嫄?媛먯? (Release 鍮뚮뱶) + ?쒋? ?⑥씪 ?몄뒪?댁뒪 裕ㅽ뀓???뺤씤 + ?쒋? SettingsService 珥덇린??+ ?ㅼ젙 濡쒕뱶 + ?쒋? ChatStorageService 蹂닿? ?뺤콉 ?ㅽ뻾 (留뚮즺 ?€???뺣━) + ?쒋? L10n ?몄뼱 珥덇린?? ?쒋? ?쒕퉬??珥덇린?? ?? ?쒋? AgentMemoryService + ?? ?쒋? ChatSessionStateService + ?? ?쒋? AppStateService + ?? ?쒋? IndexService (諛깃렇?쇱슫???뚯씪 ?몃뜳?? + ?? ?쒋? FuzzyEngine + CommandResolver + ?? ?쒋? ContextManager + ?? ?쒋? SessionTrackingService + ?? ?쒋? WorktimeReminderService + ?? ?붴? ClipboardHistoryService + ?쒋? 鍮뚰듃???몃뱾???깅줉 (136媛? + ?쒋? SchedulerService + PluginHost 珥덇린?? ?쒋? InputListener ?쒖옉 (湲€濡쒕쾶 ?ロ궎) + ?붴? ?곗쿂/?ㅼ젙/?몃젅???덈룄???앹꽦 ``` --- -## 5. 핵심 아키텍처 +## 5. ?듭떖 ?꾪궎?띿쿂 -### 5.1 런처 (Launcher) +### 5.1 ?곗쿂 (Launcher) -**검색 파이프라인**: 사용자 입력 → `CommandResolver` (접두어 매칭) → `FuzzyEngine` (퍼지 검색) → 결과 정렬 → UI 렌더링 +**寃€???뚯씠?꾨씪??*: ?ъ슜???낅젰 ??`CommandResolver` (?묐몢??留ㅼ묶) ??`FuzzyEngine` (?쇱? 寃€?? ??寃곌낵 ?뺣젹 ??UI ?뚮뜑留? +- `FuzzyEngine`: ?뚯씪 ?몃뜳??湲곕컲 ?쇱? 留ㅼ묶, ?먯닔 ?쒖쐞 +- `CommandResolver`: ?몃뱾???쇱슦??(?묐몢??`@`, `!`, `#`, `~`, `>`, `$` ?? +- `IndexService`: 諛깃렇?쇱슫???뚯씪 ?몃뜳??(`.git`, `node_modules` ???쒖쇅) -- `FuzzyEngine`: 파일 인덱스 기반 퍼지 매칭, 점수 순위 -- `CommandResolver`: 핸들러 라우팅 (접두어 `@`, `!`, `#`, `~`, `>`, `$` 등) -- `IndexService`: 백그라운드 파일 인덱싱 (`.git`, `node_modules` 등 제외) - -**위젯**: 성능 모니터, 포모도로, 메모, 날씨, 캘린더, 배터리 - -### 5.2 에이전트 (Agent Loop) +**?꾩젽**: ?깅뒫 紐⑤땲?? ?щえ?꾨줈, 硫붾え, ?좎뵪, 罹섎┛?? 諛고꽣由? +### 5.2 ?먯씠?꾪듃 (Agent Loop) ``` -사용자 메시지 - → LlmService.StreamAsync() (LLM API 호출) - → 응답 스트리밍 수신 - → 도구 호출 감지 시: - → ToolRegistry에서 도구 조회 - → 권한 확인 (AskPermissionCallback) - → 도구 실행 - → 결과를 컨텍스트에 추가 - → LLM 재호출 (반복) - → 최종 텍스트 응답 반환 +?ъ슜??硫붿떆吏€ + ??LlmService.StreamAsync() (LLM API ?몄텧) + ???묐떟 ?ㅽ듃由щ컢 ?섏떊 + ???꾧뎄 ?몄텧 媛먯? ?? + ??ToolRegistry?먯꽌 ?꾧뎄 議고쉶 + ??沅뚰븳 ?뺤씤 (AskPermissionCallback) + ???꾧뎄 ?ㅽ뻾 + ??寃곌낵瑜?而⑦뀓?ㅽ듃??異붽? + ??LLM ?ы샇異?(諛섎났) + ??理쒖쥌 ?띿뒪???묐떟 諛섑솚 ``` -**핵심 클래스**: -- `AgentLoopService` — 루프 엔진 (반복, 일시정지/재개, 이벤트 발행) -- `AxAgentExecutionEngine` — 도구 실행 조율 -- `AgentLoopParallelExecution` — 병렬 도구 실행 -- `AgentLoopTransitions` / `.Execution` — 상태 전이 로직 -- `ToolRegistry` — 도구 등록/조회 -- `ContextCondenser` — 컨텍스트 압축 (토큰 관리) +**?듭떖 ?대옒??*: +- `AgentLoopService` ??猷⑦봽 ?붿쭊 (諛섎났, ?쇱떆?뺤?/?ш컻, ?대깽??諛쒗뻾) +- `AxAgentExecutionEngine` ???꾧뎄 ?ㅽ뻾 議곗쑉 +- `AgentLoopParallelExecution` ??蹂묐젹 ?꾧뎄 ?ㅽ뻾 +- `AgentLoopTransitions` / `.Execution` ???곹깭 ?꾩씠 濡쒖쭅 +- `ToolRegistry` ???꾧뎄 ?깅줉/議고쉶 +- `ContextCondenser` ??而⑦뀓?ㅽ듃 ?뺤텞 (?좏겙 愿€由? -**도구 카테고리** (114개): -| 카테고리 | 예시 | +**?꾧뎄 移댄뀒怨좊━** (114媛?: +| 移댄뀒怨좊━ | ?덉떆 | |---------|------| -| 파일 I/O | FileReadTool, FileEditTool, FileManageTool, FileWriteTool | -| 검색 | GlobTool, GrepTool, CodeSearchTool, FileSearchTool | -| 문서 | DocumentReaderTool, ExcelSkill, DocxSkill, PptxSkill, CsvSkill, HtmlSkill | -| 코드 | BuildRunTool, SnippetRunnerTool, CodeReviewTool, TestLoopTool, LspTool | -| 데이터 | JsonTool, XmlTool, SqlTool, DataPivotTool, RegexTool | -| 시스템 | ProcessTool, EnvTool, ZipTool, ClipboardTool | -| 계획/추적 | TodoWriteTool, TaskTrackerTool, CheckpointTool, PlaybookTool | -| 사용자 | UserAskTool, SuggestActionsTool, NotifyTool | +| ?뚯씪 I/O | FileReadTool, FileEditTool, FileManageTool, FileWriteTool | +| 寃€??| GlobTool, GrepTool, CodeSearchTool, FileSearchTool | +| 臾몄꽌 | DocumentReaderTool, ExcelSkill, DocxSkill, PptxSkill, CsvSkill, HtmlSkill | +| 肄붾뱶 | BuildRunTool, SnippetRunnerTool, CodeReviewTool, TestLoopTool, LspTool | +| ?곗씠??| JsonTool, XmlTool, SqlTool, DataPivotTool, RegexTool | +| ?쒖뒪??| ProcessTool, EnvTool, ZipTool, ClipboardTool | +| 怨꾪쉷/異붿쟻 | TodoWriteTool, TaskTrackerTool, CheckpointTool, PlaybookTool | +| ?ъ슜??| UserAskTool, SuggestActionsTool, NotifyTool | | MCP | McpTool, McpListResourcesTool, McpReadResourceTool | -### 5.3 LLM 서비스 - -**지원 공급자**: -| 서비스 | 설명 | +### 5.3 LLM ?쒕퉬?? +**吏€??怨듦툒??*: +| ?쒕퉬??| ?ㅻ챸 | |--------|------| -| `claude` / `sigmoid` | Anthropic Claude (Sigmoid API 경유) | +| `claude` / `sigmoid` | Anthropic Claude (Sigmoid API 寃쎌쑀) | | `gemini` | Google Gemini API | -| `vllm` | OpenAI 호환 vLLM (IBM CP4D 지원 포함) | -| `ollama` | 로컬 Ollama 모델 | +| `vllm` | OpenAI ?명솚 vLLM (IBM CP4D 吏€???ы븿) | +| `ollama` | 濡쒖뺄 Ollama 紐⑤뜽 | -**모델 라우팅**: `ModelRouterService`를 통한 오버라이드 스택 — 대화 중 모델/서비스를 동적으로 전환 가능 +**紐⑤뜽 ?쇱슦??*: `ModelRouterService`瑜??듯븳 ?ㅻ쾭?쇱씠???ㅽ깮 ???€??以?紐⑤뜽/?쒕퉬?ㅻ? ?숈쟻?쇰줈 ?꾪솚 媛€?? +**?좏겙 愿€由?*: `TokenEstimator`濡?而⑦뀓?ㅽ듃 湲몄씠 異붿젙, ?ㅻ쾭?뚮줈????`ContextCondenser`媛€ ?먮룞 ?뺤텞 -**토큰 관리**: `TokenEstimator`로 컨텍스트 길이 추정, 오버플로우 시 `ContextCondenser`가 자동 압축 - -### 5.4 대화 저장소 - -- `ChatStorageService`: SQLite 기반 대화 영속화 -- `ChatSessionStateService`: 메모리 내 세션 상태 관리 -- `ChatConversation`: 메시지 목록 + 실행 이벤트 타임라인 +### 5.4 ?€???€?μ냼 +- `ChatStorageService`: SQLite 湲곕컲 ?€???곸냽??- `ChatSessionStateService`: 硫붾え由????몄뀡 ?곹깭 愿€由?- `ChatConversation`: 硫붿떆吏€ 紐⑸줉 + ?ㅽ뻾 ?대깽???€?꾨씪?? --- -## 6. UI 계층 +## 6. UI 怨꾩링 -### 주요 윈도우 - -| 윈도우 | 역할 | +### 二쇱슂 ?덈룄?? +| ?덈룄??| ??븷 | |--------|------| -| `LauncherWindow` | 메인 런처 (검색, 위젯, 결과 목록) | -| `ChatWindow` | AI 에이전트 대화 (채팅/Cowork/코드 탭) | -| `DockBarWindow` | 독 바 (시스템 리소스, 빠른 접근) | -| `SettingsWindow` | 설정 관리 | -| `AgentSettingsWindow` | 에이전트 전용 설정 | -| `AgentStatsDashboardWindow` | 에이전트 통계 대시보드 | -| `SkillEditorWindow` | 스킬 편집기 | -| `SkillGalleryWindow` | 스킬 갤러리 | -| `TrayMenuWindow` | 시스템 트레이 메뉴 | -| `PreviewWindow` | 문서 미리보기 (WebView2) | +| `LauncherWindow` | 硫붿씤 ?곗쿂 (寃€?? ?꾩젽, 寃곌낵 紐⑸줉) | +| `ChatWindow` | AI ?먯씠?꾪듃 ?€??(梨꾪똿/Cowork/肄붾뱶 ?? | +| `DockBarWindow` | ??諛?(?쒖뒪??由ъ냼?? 鍮좊Ⅸ ?묎렐) | +| `SettingsWindow` | ?ㅼ젙 愿€由?| +| `AgentSettingsWindow` | ?먯씠?꾪듃 ?꾩슜 ?ㅼ젙 | +| `AgentStatsDashboardWindow` | ?먯씠?꾪듃 ?듦퀎 ?€?쒕낫??| +| `SkillEditorWindow` | ?ㅽ궗 ?몄쭛湲?| +| `SkillGalleryWindow` | ?ㅽ궗 媛ㅻ윭由?| +| `TrayMenuWindow` | ?쒖뒪???몃젅??硫붾돱 | +| `PreviewWindow` | 臾몄꽌 誘몃━蹂닿린 (WebView2) | -### ChatWindow 분할 구조 +### ChatWindow 遺꾪븷 援ъ“ -`ChatWindow.xaml.cs`는 partial class로 기능별 분할: +`ChatWindow.xaml.cs`??partial class濡?湲곕뒫蹂?遺꾪븷: -| 파일 | 역할 | +| ?뚯씪 | ??븷 | |------|------| -| `ChatWindow.xaml.cs` | 메인 오케스트레이션, 스트리밍, 입력 처리 | -| `ChatWindow.AgentEventProcessor.cs` | 에이전트 이벤트 수신/라우팅 | -| `ChatWindow.AgentEventRendering.cs` | 에이전트 이벤트 배너/카드 렌더링 | -| `ChatWindow.ComposerQueuePresentation.cs` | 작성기 큐 UI | -| `ChatWindow.ContextUsagePresentation.cs` | 컨텍스트 사용량 링/팝업 | -| `ChatWindow.ConversationFilterPresentation.cs` | 대화 필터링 | -| `ChatWindow.ConversationListPresentation.cs` | 사이드바 대화 목록 | -| `ChatWindow.ConversationManagementPresentation.cs` | 대화 생성/삭제/관리 | -| `ChatWindow.FileBrowserPresentation.cs` | 파일 브라우저 UI | -| `ChatWindow.FooterPresentation.cs` | 하단 바 (폴더, 권한) | -| `ChatWindow.GitBranchPresentation.cs` | Git 브랜치 표시/전환 | -| `ChatWindow.LiveProgressPresentation.cs` | 실시간 진행 상태 | -| `ChatWindow.MessageBubblePresentation.cs` | 메시지 버블 렌더링 | -| `ChatWindow.MessageInteractions.cs` | 메시지 복사/편집/재전송 | -| `ChatWindow.PermissionPresentation.cs` | 권한 팝업/배너 UI | -| `ChatWindow.PlanApprovalPresentation.cs` | 계획 승인 카드 | -| `ChatWindow.PopupPresentation.cs` | 공통 팝업 구성 | -| `ChatWindow.PreviewPresentation.cs` | 파일 미리보기 탭 | -| `ChatWindow.SelectionPopupPresentation.cs` | 워크트리 선택 팝업 | -| `ChatWindow.SidebarInteractionPresentation.cs` | 사이드바 상호작용 | -| `ChatWindow.StatusPresentation.cs` | 상태 배지/스트립 | -| `ChatWindow.SurfaceVisualPresentation.cs` | 시각 효과 (글로우, 펄스 등) | -| `ChatWindow.TaskSummary.cs` | 작업 요약 카드 | -| `ChatWindow.TimelinePresentation.cs` | 타임라인 정렬, 캐시, 이벤트 필터링 | -| `ChatWindow.TopicPresetPresentation.cs` | 주제 프리셋 UI | -| `ChatWindow.TranscriptHost.cs` | 트랜스크립트 호스트 컨테이너 | -| `ChatWindow.TranscriptPolicy.cs` | 트랜스크립트 표시 정책 | -| `ChatWindow.TranscriptRenderExecution.cs` | 트랜스크립트 렌더 실행 | -| `ChatWindow.TranscriptRenderPlanner.cs` | 트랜스크립트 렌더 계획 | -| `ChatWindow.TranscriptRendering.cs` | 트랜스크립트 렌더링 | -| `ChatWindow.TranscriptVirtualization.cs` | 트랜스크립트 가상화 (대규모 대화) | -| `ChatWindow.UserAskPresentation.cs` | 사용자 질문 인라인 카드 | -| `ChatWindow.VisualInteractionHelpers.cs` | 시각 상호작용 헬퍼 | +| `ChatWindow.xaml.cs` | 硫붿씤 ?ㅼ??ㅽ듃?덉씠?? ?ㅽ듃由щ컢, ?낅젰 泥섎━ | +| `ChatWindow.AgentEventProcessor.cs` | ?먯씠?꾪듃 ?대깽???섏떊/?쇱슦??| +| `ChatWindow.AgentEventRendering.cs` | ?먯씠?꾪듃 ?대깽??諛곕꼫/移대뱶 ?뚮뜑留?| +| `ChatWindow.ComposerQueuePresentation.cs` | ?묒꽦湲???UI | +| `ChatWindow.ContextUsagePresentation.cs` | 而⑦뀓?ㅽ듃 ?ъ슜??留??앹뾽 | +| `ChatWindow.ConversationFilterPresentation.cs` | ?€???꾪꽣留?| +| `ChatWindow.ConversationListPresentation.cs` | ?ъ씠?쒕컮 ?€??紐⑸줉 | +| `ChatWindow.ConversationManagementPresentation.cs` | ?€???앹꽦/??젣/愿€由?| +| `ChatWindow.FileBrowserPresentation.cs` | ?뚯씪 釉뚮씪?곗? UI | +| `ChatWindow.FooterPresentation.cs` | ?섎떒 諛?(?대뜑, 沅뚰븳) | +| `ChatWindow.GitBranchPresentation.cs` | Git 釉뚮옖移??쒖떆/?꾪솚 | +| `ChatWindow.LiveProgressPresentation.cs` | ?ㅼ떆媛?吏꾪뻾 ?곹깭 | +| `ChatWindow.MessageBubblePresentation.cs` | 硫붿떆吏€ 踰꾨툝 ?뚮뜑留?| +| `ChatWindow.MessageInteractions.cs` | 硫붿떆吏€ 蹂듭궗/?몄쭛/?ъ쟾??| +| `ChatWindow.PermissionPresentation.cs` | 沅뚰븳 ?앹뾽/諛곕꼫 UI | +| `ChatWindow.PlanApprovalPresentation.cs` | 怨꾪쉷 ?뱀씤 移대뱶 | +| `ChatWindow.PopupPresentation.cs` | 怨듯넻 ?앹뾽 援ъ꽦 | +| `ChatWindow.PreviewPresentation.cs` | ?뚯씪 誘몃━蹂닿린 ??| +| `ChatWindow.SelectionPopupPresentation.cs` | ?뚰겕?몃━ ?좏깮 ?앹뾽 | +| `ChatWindow.SidebarInteractionPresentation.cs` | ?ъ씠?쒕컮 ?곹샇?묒슜 | +| `ChatWindow.StatusPresentation.cs` | ?곹깭 諛곗?/?ㅽ듃由?| +| `ChatWindow.SurfaceVisualPresentation.cs` | ?쒓컖 ?④낵 (湲€濡쒖슦, ?꾩뒪 ?? | +| `ChatWindow.TaskSummary.cs` | ?묒뾽 ?붿빟 移대뱶 | +| `ChatWindow.TimelinePresentation.cs` | ?€?꾨씪???뺣젹, 罹먯떆, ?대깽???꾪꽣留?| +| `ChatWindow.TopicPresetPresentation.cs` | 二쇱젣 ?꾨━??UI | +| `ChatWindow.TranscriptHost.cs` | ?몃옖?ㅽ겕由쏀듃 ?몄뒪??而⑦뀒?대꼫 | +| `ChatWindow.TranscriptPolicy.cs` | ?몃옖?ㅽ겕由쏀듃 ?쒖떆 ?뺤콉 | +| `ChatWindow.TranscriptRenderExecution.cs` | ?몃옖?ㅽ겕由쏀듃 ?뚮뜑 ?ㅽ뻾 | +| `ChatWindow.TranscriptRenderPlanner.cs` | ?몃옖?ㅽ겕由쏀듃 ?뚮뜑 怨꾪쉷 | +| `ChatWindow.TranscriptRendering.cs` | ?몃옖?ㅽ겕由쏀듃 ?뚮뜑留?| +| `ChatWindow.TranscriptVirtualization.cs` | ?몃옖?ㅽ겕由쏀듃 媛€?곹솕 (?€洹쒕え ?€?? | +| `ChatWindow.UserAskPresentation.cs` | ?ъ슜??吏덈Ц ?몃씪??移대뱶 | +| `ChatWindow.VisualInteractionHelpers.cs` | ?쒓컖 ?곹샇?묒슜 ?ы띁 | -### 테마 시스템 +### ?뚮쭏 ?쒖뒪?? +9媛??뚮쭏 XAML 由ъ냼???뺤뀛?덈━: `Dark`, `Light`, `OLED`, `Nord`, `Monokai`, `Catppuccin`, `Sepia`, `Alfred`, `AlfredLight` -9개 테마 XAML 리소스 딕셔너리: `Dark`, `Light`, `OLED`, `Nord`, `Monokai`, `Catppuccin`, `Sepia`, `Alfred`, `AlfredLight` - -런타임 테마 전환: `SettingsService.Settings.Launcher.Theme` 변경 → 리소스 딕셔너리 교체 +?고????뚮쭏 ?꾪솚: `SettingsService.Settings.Launcher.Theme` 蹂€寃???由ъ냼???뺤뀛?덈━ 援먯껜 --- -## 7. 설정 구조 (AppSettings) +## 7. ?ㅼ젙 援ъ“ (AppSettings) -### 최상위 설정 +### 理쒖긽???ㅼ젙 -| 속성 | 기본값 | 설명 | +| ?띿꽦 | 湲곕낯媛?| ?ㅻ챸 | |------|--------|------| -| `AiEnabled` | true | AI 기능 활성화 | -| `OperationMode` | "internal" | 운영 모드 (internal/external) | -| `Hotkey` | "Alt+Space" | 런처 단축키 | -| `CleanupPeriodDays` | 30 | 대화 보관 기간 (일) | -| `InternalModeEnabled` | true | 사내 모드 여부 | +| `AiEnabled` | true | AI 湲곕뒫 ?쒖꽦??| +| `OperationMode` | "internal" | ?댁쁺 紐⑤뱶 (internal/external) | +| `Hotkey` | "Alt+Space" | ?곗쿂 ?⑥텞??| +| `CleanupPeriodDays` | 30 | ?€??蹂닿? 湲곌컙 (?? | +| `InternalModeEnabled` | true | ?щ궡 紐⑤뱶 ?щ? | -### LauncherSettings (중첩) +### LauncherSettings (以묒꺽) -| 그룹 | 주요 속성 | +| 洹몃9 | 二쇱슂 ?띿꽦 | |------|----------| -| 표시 | `Theme`, `Opacity`, `Position`, `Width`, `MaxResults` | -| 글로우 | `EnableRainbowGlow`, `EnableSelectionGlow`, `ShowLauncherBorder` | -| 위젯 | `ShowWidgetPerf`, `ShowWidgetPomo`, `ShowWidgetNote`, `ShowWidgetWeather`, `ShowWidgetCalendar`, `ShowWidgetBattery` | -| 독 바 | `DockBarItems`, `DockBarAutoShow`, `DockBarOpacity`, `DockBarRainbowGlow` | -| 기능 | `EnableFavorites`, `EnableRecent`, `EnableActionMode`, `EnableClipboardAutoCategory` | +| ?쒖떆 | `Theme`, `Opacity`, `Position`, `Width`, `MaxResults` | +| 湲€濡쒖슦 | `EnableRainbowGlow`, `EnableSelectionGlow`, `ShowLauncherBorder` | +| ?꾩젽 | `ShowWidgetPerf`, `ShowWidgetPomo`, `ShowWidgetNote`, `ShowWidgetWeather`, `ShowWidgetCalendar`, `ShowWidgetBattery` | +| ??諛?| `DockBarItems`, `DockBarAutoShow`, `DockBarOpacity`, `DockBarRainbowGlow` | +| 湲곕뒫 | `EnableFavorites`, `EnableRecent`, `EnableActionMode`, `EnableClipboardAutoCategory` | -### LlmSettings (중첩) +### LlmSettings (以묒꺽) -에이전트의 LLM 연결 설정: 서비스 선택, 모델, API 키 (DPAPI 암호화), 엔드포인트, 온도, 최대 토큰 등 - -| 속성 | 기본값 | 설명 | +?먯씠?꾪듃??LLM ?곌껐 ?ㅼ젙: ?쒕퉬???좏깮, 紐⑤뜽, API ??(DPAPI ?뷀샇??, ?붾뱶?ъ씤?? ?⑤룄, 理쒕? ?좏겙 ?? +| ?띿꽦 | 湲곕낯媛?| ?ㅻ챸 | |------|--------|------| -| `UseAutomaticProfileTemperature` | true | 등록 모델 프로파일의 자동 temperature 정책 | -| `EnableDetailedLog` | false | 워크플로우 상세 로그 (LLM 요청/응답, 도구 이력) | -| `DetailedLogRetentionDays` | 3 | 상세 로그 보관 기간 (일) | -| `EnableRawLlmLog` | false | LLM 요청/응답 원문 기록 (디버깅용) | +| `UseAutomaticProfileTemperature` | true | ?깅줉 紐⑤뜽 ?꾨줈?뚯씪???먮룞 temperature ?뺤콉 | +| `EnableDetailedLog` | false | ?뚰겕?뚮줈???곸꽭 濡쒓렇 (LLM ?붿껌/?묐떟, ?꾧뎄 ?대젰) | +| `DetailedLogRetentionDays` | 3 | ?곸꽭 濡쒓렇 蹂닿? 湲곌컙 (?? | +| `EnableRawLlmLog` | false | LLM ?붿껌/?묐떟 ?먮Ц 湲곕줉 (?붾쾭源낆슜) | -### RegisteredModel 실행 프로파일 +### RegisteredModel ?ㅽ뻾 ?꾨줈?뚯씪 -모델별 `ExecutionProfile`로 도구 호출 강도, 재시도, 메모리/압축 주입량을 조절: +紐⑤뜽蹂?`ExecutionProfile`濡??꾧뎄 ?몄텧 媛뺣룄, ?ъ떆?? 硫붾え由??뺤텞 二쇱엯?됱쓣 議곗젅: -| 프로파일 | 설명 | +| ?꾨줈?뚯씪 | ?ㅻ챸 | |---------|------| -| `balanced` | 기본 균형 모드 | -| `tool_call_strict` | 도구 호출 강제/엄격 모드 | -| `reasoning_first` | 추론 우선 모드 | -| `fast_readonly` | 빠른 읽기 전용 모드 | -| `document_heavy` | 문서 처리 집중 모드 | +| `balanced` | 湲곕낯 洹좏삎 紐⑤뱶 | +| `tool_call_strict` | ?꾧뎄 ?몄텧 媛뺤젣/?꾧꺽 紐⑤뱶 | +| `reasoning_first` | 異붾줎 ?곗꽑 紐⑤뱶 | +| `fast_readonly` | 鍮좊Ⅸ ?쎄린 ?꾩슜 紐⑤뱶 | +| `document_heavy` | 臾몄꽌 泥섎━ 吏묒쨷 紐⑤뱶 | --- -## 8. 플러그인 시스템 - +## 8. ?뚮윭洹몄씤 ?쒖뒪?? ### SDK (AxCopilot.SDK) ```csharp public interface IActionHandler { - string? Prefix { get; } // 접두어 (null이면 퍼지 검색만) + string? Prefix { get; } // ?묐몢??(null?대㈃ ?쇱? 寃€?됰쭔) PluginMetadata Metadata { get; } - Task> GetItemsAsync(string query, CancellationToken ct); - Task ExecuteAsync(LauncherItem item, CancellationToken ct); -} -``` + Task +## claude-code식 transcript 표시 구조 정리 -### 개발 방법 +- 업데이트: 2026-04-09 13:05 (KST) +- AgentTranscriptDisplayCatalog를 row presentation 중심으로 재구성해 hinking / waiting / compact / tool activity / permission / tool result / status를 개별 transcript row 의미로 다룰 수 있게 정리했습니다. +- ChatWindow.AgentEventRendering은 process feed 계열 이벤트를 같은 GroupKey 단위로 병합해, 긴 Cowork/Code 실행 중 append 수를 줄이면서도 주요 활동 흐름이 기본 transcript에 남도록 조정했습니다. +- PermissionRequestPresentationCatalog, ToolResultPresentationCatalog를 다시 정리해 권한 요청과 도구 결과를 행위/상태 기준으로 구분하고, transcript 렌더와 popup이 같은 메타를 공유하도록 맞췄습니다. +- ChatWindow.FooterPresentation은 execution event가 생긴 뒤에는 프리셋 안내 카드를 자동으로 숨기도록 바꿔 결과/진행 화면을 덮지 않게 했고, Cowork/Code 입력 워터마크와 footer 기본 문구도 정상 한국어 기준으로 다시 정리했습니다. +- +ender_messages 성능 로그에는 processFeedAppends, processFeedMerges, +owKindCounts를 함께 남겨 %APPDATA%\\AxCopilot\\perf 기준으로 transcript grouping 효과를 수치로 비교할 수 있게 했습니다. -1. `AxCopilot.SDK` 참조하여 `IActionHandler` 구현 -2. 빌드된 `.dll`을 `settings.json`의 `Plugins` 배열에 경로 등록 -3. `PluginHost`가 앱 시작 시 동적 로드 +## 9. 鍮뚮뱶 諛??ㅽ뻾 ---- - -## 9. 빌드 및 실행 - -### 개발 빌드 +### 媛쒕컻 鍮뚮뱶 ```bash dotnet build src/AxCopilot/AxCopilot.csproj ``` -### 릴리스 빌드 (단일 파일) +### 由대━??鍮뚮뱶 (?⑥씪 ?뚯씪) ```bash dotnet publish src/AxCopilot/AxCopilot.csproj -c Release -r win-x64 --self-contained ``` -릴리스 빌드 옵션: -- `PublishSingleFile`: 단일 실행 파일 -- `EnableCompressionInSingleFile`: 압축 적용 -- `PublishReadyToRun`: AOT 프리컴파일 -- `DebugType=none`: 디버그 심볼 제거 -- `TrimMode=partial`: IL 트리밍 - -### 테스트 - +由대━??鍮뚮뱶 ?듭뀡: +- `PublishSingleFile`: ?⑥씪 ?ㅽ뻾 ?뚯씪 +- `EnableCompressionInSingleFile`: ?뺤텞 ?곸슜 +- `PublishReadyToRun`: AOT ?꾨━而댄뙆??- `DebugType=none`: ?붾쾭洹??щ낵 ?쒓굅 +- `TrimMode=partial`: IL ?몃━諛? +### ?뚯뒪?? ```bash dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj ``` --- -## 10. 버전 관리 - -- `AxCopilot.csproj`의 `` 태그 하나만 변경하면 앱 전체에 반영 -- 설정 스키마 버전은 `SettingsService.cs` → `CurrentSettingsVersion`에서 별도 관리 -- 마이그레이션: `SettingsService`가 이전 버전 설정 파일을 자동 업그레이드 - +## 10. 踰꾩쟾 愿€由? +- `AxCopilot.csproj`??`` ?쒓렇 ?섎굹留?蹂€寃쏀븯硫????꾩껜??諛섏쁺 +- ?ㅼ젙 ?ㅽ궎留?踰꾩쟾?€ `SettingsService.cs` ??`CurrentSettingsVersion`?먯꽌 蹂꾨룄 愿€由?- 留덉씠洹몃젅?댁뀡: `SettingsService`媛€ ?댁쟾 踰꾩쟾 ?ㅼ젙 ?뚯씪???먮룞 ?낃렇?덉씠?? --- -## 11. 보안 +## 11. 蹂댁븞 -| 항목 | 구현 | +| ??ぉ | 援ы쁽 | |------|------| -| API 키 저장 | DPAPI 암호화 (System.Security.Cryptography.ProtectedData) | -| 키 관리 도구 | AxKeyEncryptor (별도 유틸리티) | -| 안티 탬퍼 | 디버거/디컴파일러 감지 (Release 빌드, `Security/AntiTamper.cs`) | -| Unsafe 코드 | `AllowUnsafeBlocks=true` (ScreenCaptureHandler 포인터 연산용) | +| API ???€??| DPAPI ?뷀샇??(System.Security.Cryptography.ProtectedData) | +| ??愿€由??꾧뎄 | AxKeyEncryptor (蹂꾨룄 ?좏떥由ы떚) | +| ?덊떚 ?ы띁 | ?붾쾭嫄??붿뺨?뚯씪??媛먯? (Release 鍮뚮뱶, `Security/AntiTamper.cs`) | +| Unsafe 肄붾뱶 | `AllowUnsafeBlocks=true` (ScreenCaptureHandler ?ъ씤???곗궛?? | --- -## 12. 성능 최적화 내역 +## 12. ?깅뒫 理쒖쟻???댁뿭 -### 유휴 CPU 최적화 (2026-04-09) +### ?좏쑕 CPU 理쒖쟻??(2026-04-09) -| 대상 | 변경 전 | 변경 후 | +| ?€??| 蹂€寃???| 蹂€寃???| |------|---------|---------| -| PerformanceMonitorService 폴링 | 2초 | 5초 | -| 위젯 타이머 | 1초 | 3초 | -| 레인보우 글로우 타이머 | 150ms | 300ms | -| ServerStatusService 핑 | 15초 | 60초 | +| PerformanceMonitorService ?대쭅 | 2珥?| 5珥?| +| ?꾩젽 ?€?대㉧ | 1珥?| 3珥?| +| ?덉씤蹂댁슦 湲€濡쒖슦 ?€?대㉧ | 150ms | 300ms | +| ServerStatusService ??| 15珥?| 60珥?| -### 스트리밍 렌더링 최적화 (2026-04-09) +### ?ㅽ듃由щ컢 ?뚮뜑留?理쒖쟻??(2026-04-09) -- **TypingTimer**: 50ms → 80ms, `string.Concat` → `char[]` 버퍼 재사용 -- **CursorTimer**: 전체 문자열 재생성 → 마지막 문자만 교체 -- **StringBuilder.ToString()**: 30ms 최소 간격 쓰로틀링 -- **RenderMessages**: 스트리밍 중 불필요한 전체 재렌더링 방지 (조기 반환) -- **타임라인 이벤트**: 접힌 모드에서 연속 동일 ToolCall 병합 +- **TypingTimer**: 50ms ??80ms, `string.Concat` ??`char[]` 踰꾪띁 ?ъ궗??- **CursorTimer**: ?꾩껜 臾몄옄???ъ깮????留덉?留?臾몄옄留?援먯껜 +- **StringBuilder.ToString()**: 30ms 理쒖냼 媛꾧꺽 ?곕줈?€留?- **RenderMessages**: ?ㅽ듃由щ컢 以?遺덊븘?뷀븳 ?꾩껜 ?щ젋?붾쭅 諛⑹? (議곌린 諛섑솚) +- **?€?꾨씪???대깽??*: ?묓엺 紐⑤뱶?먯꽌 ?곗냽 ?숈씪 ToolCall 蹂묓빀 -### 런타임 안정성 수정 (2026-04-09) +### ?고????덉젙???섏젙 (2026-04-09) -| 파일 | 수정 내용 | +| ?뚯씪 | ?섏젙 ?댁슜 | |------|----------| -| `CsvSkill.cs` | JSON 배열 첫 요소 `ValueKind` 검증 추가 | -| `HtmlSkill.cs` | gradient `Split(',')` 결과 `Length >= 2` 가드 추가 | -| `ChatWindow.xaml.cs` | `ParseGenericAction` 빈 배열 가드, `ShowDropActionMenu` null 가드, `GetAgentLoop` `.FirstOrDefault()` 전환 | -| `ChatWindow.GitBranchPresentation.cs` | async void 핸들러 try/catch 보호 | -| `ChatWindow.xaml.cs` (BtnGitBranch_Click) | async void 핸들러 try/catch 보호 | +| `CsvSkill.cs` | JSON 諛곗뿴 泥??붿냼 `ValueKind` 寃€利?異붽? | +| `HtmlSkill.cs` | gradient `Split(',')` 寃곌낵 `Length >= 2` 媛€??異붽? | +| `ChatWindow.xaml.cs` | `ParseGenericAction` 鍮?諛곗뿴 媛€?? `ShowDropActionMenu` null 媛€?? `GetAgentLoop` `.FirstOrDefault()` ?꾪솚 | +| `ChatWindow.GitBranchPresentation.cs` | async void ?몃뱾??try/catch 蹂댄샇 | +| `ChatWindow.xaml.cs` (BtnGitBranch_Click) | async void ?몃뱾??try/catch 蹂댄샇 | -### UI 스레드 부하 최적화 2차 (2026-04-09) +### UI ?ㅻ젅??遺€??理쒖쟻??2李?(2026-04-09) -| 대상 | 변경 전 | 변경 후 | 효과 | +| ?€??| 蹂€寃???| 蹂€寃???| ?④낵 | |------|---------|---------|------| -| 스크롤 애니메이션 | 매번 새 16ms 타이머 생성 | 재사용 32ms 타이머 1개 | GC 압력 + 타이머 누적 해소 | -| 사이드바 애니메이션 | 매번 새 10ms 타이머 생성 | 재사용 32ms 타이머 1개 | 동일 | -| Git 브랜치 UI | `Dispatcher.Invoke` (블로킹) | `Dispatcher.InvokeAsync` (논블로킹) | UI 스레드 차단 해소 | -| 토큰 사용량 호 | 매 250ms PathGeometry 재생성 | 1% 미만 변화 시 렌더링 생략 | 불필요한 레이아웃 연산 제거 | -| 대화 검색 타이머 | 140ms | 300ms | 초당 7회 → 3회 | -| 에이전트 이벤트 타이머 | 140ms (스트리밍: 300/420) | 200ms (스트리밍: 350/500) | 이벤트 처리 빈도 완화 | -| 반응형 레이아웃 타이머 | 120ms | 250ms | 리사이즈 디바운스 강화 | -| 대화 목록 LINQ | Where×2 + Count×3 = 리스트 5회 순회 | Where 1회 병합 + 단일 루프 카운트 | 할당/순회 대폭 감소 | +| ?ㅽ겕濡??좊땲硫붿씠??| 留ㅻ쾲 ??16ms ?€?대㉧ ?앹꽦 | ?ъ궗??32ms ?€?대㉧ 1媛?| GC ?뺣젰 + ?€?대㉧ ?꾩쟻 ?댁냼 | +| ?ъ씠?쒕컮 ?좊땲硫붿씠??| 留ㅻ쾲 ??10ms ?€?대㉧ ?앹꽦 | ?ъ궗??32ms ?€?대㉧ 1媛?| ?숈씪 | +| Git 釉뚮옖移?UI | `Dispatcher.Invoke` (釉붾줈?? | `Dispatcher.InvokeAsync` (?쇰툝濡쒗궧) | UI ?ㅻ젅??李⑤떒 ?댁냼 | +| ?좏겙 ?ъ슜????| 留?250ms PathGeometry ?ъ깮??| 1% 誘몃쭔 蹂€?????뚮뜑留??앸왂 | 遺덊븘?뷀븳 ?덉씠?꾩썐 ?곗궛 ?쒓굅 | +| ?€??寃€???€?대㉧ | 140ms | 300ms | 珥덈떦 7????3??| +| ?먯씠?꾪듃 ?대깽???€?대㉧ | 140ms (?ㅽ듃由щ컢: 300/420) | 200ms (?ㅽ듃由щ컢: 350/500) | ?대깽??泥섎━ 鍮덈룄 ?꾪솕 | +| 諛섏쓳???덉씠?꾩썐 ?€?대㉧ | 120ms | 250ms | 由ъ궗?댁쫰 ?붾컮?댁뒪 媛뺥솕 | +| ?€??紐⑸줉 LINQ | Where횞2 + Count횞3 = 由ъ뒪??5???쒗쉶 | Where 1??蹂묓빀 + ?⑥씪 猷⑦봽 移댁슫??| ?좊떦/?쒗쉶 ?€??媛먯냼 | -### 구조적 메모리/안정성 수정 (2026-04-09) +### 援ъ“??硫붾え由??덉젙???섏젙 (2026-04-09) -| 문제 | 위치 | 수정 | +| 臾몄젣 | ?꾩튂 | ?섏젙 | |------|------|------| -| Events 컬렉션 무한 성장 | `AgentLoopService.cs` | 500개 초과 시 오래된 이벤트 자동 제거 | -| 파일 브라우저 타이머 좀비 | `ChatWindow.FileBrowserPresentation.cs` | 매번 새 타이머 생성 → 재사용 패턴 | -| 엘리먼트 캐시 미정리 | `ChatWindow.TranscriptVirtualization.cs` | 보유 한도 240→120, 1.5배 초과 시 정리 | -| WorkflowAnalyzer UI 블로킹 | `WorkflowAnalyzerWindow.xaml.cs` | `Dispatcher.Invoke` → `InvokeAsync` | +| Events 而щ젆??臾댄븳 ?깆옣 | `AgentLoopService.cs` | 500媛?珥덇낵 ???ㅻ옒???대깽???먮룞 ?쒓굅 | +| ?뚯씪 釉뚮씪?곗? ?€?대㉧ 醫€鍮?| `ChatWindow.FileBrowserPresentation.cs` | 留ㅻ쾲 ???€?대㉧ ?앹꽦 ???ъ궗???⑦꽩 | +| ?섎━癒쇳듃 罹먯떆 誘몄젙由?| `ChatWindow.TranscriptVirtualization.cs` | 蹂댁쑀 ?쒕룄 240??20, 1.5諛?珥덇낵 ???뺣━ | +| WorkflowAnalyzer UI 釉붾줈??| `WorkflowAnalyzerWindow.xaml.cs` | `Dispatcher.Invoke` ??`InvokeAsync` | -### 구조적 리팩토링 P1 (2026-04-09) +### 援ъ“??由ы뙥?좊쭅 P1 (2026-04-09) -| 대상 | 파일 | 변경 | +| ?€??| ?뚯씪 | 蹂€寃?| |------|------|------| -| 인크리멘탈 렌더 hiddenCount 안정화 | `ChatWindow.TranscriptRenderPlanner.cs` | 스트리밍 중 hiddenCount 감소 차단 → prefix 키 불일치로 인한 전체 재빌드 폴백 방지 | -| 비가시 렌더 차단 | `ChatWindow.TranscriptRendering.cs` | 최소화/숨김 상태에서 RenderMessages 즉시 반환 → 불필요한 UI 재구축 제거 | -| ConversationList 이벤트 위임 | `ChatWindow.ConversationListPresentation.cs` | 항목당 5개 람다 핸들러 → ConversationPanel에 단일 위임 핸들러 (Tag 기반 분기). 탭 전환 시 250개 핸들러 누적 해소 | -| TopicPreset 이벤트 위임 | `ChatWindow.TopicPresetPresentation.cs` | 카드당 3개 람다 핸들러 → TopicButtonPanel에 단일 위임 핸들러. 탭 전환 시 45개 핸들러 누적 해소 | -| 공통 VisualTree 헬퍼 | `ChatWindow.VisualInteractionHelpers.cs` | `FindAncestorWithTag`, `FindAncestor` 유틸 추가 | +| ?명겕由щ찘???뚮뜑 hiddenCount ?덉젙??| `ChatWindow.TranscriptRenderPlanner.cs` | ?ㅽ듃由щ컢 以?hiddenCount 媛먯냼 李⑤떒 ??prefix ??遺덉씪移섎줈 ?명븳 ?꾩껜 ?щ퉴???대갚 諛⑹? | +| 鍮꾧????뚮뜑 李⑤떒 | `ChatWindow.TranscriptRendering.cs` | 理쒖냼???④? ?곹깭?먯꽌 RenderMessages 利됱떆 諛섑솚 ??遺덊븘?뷀븳 UI ?ш뎄異??쒓굅 | +| ConversationList ?대깽???꾩엫 | `ChatWindow.ConversationListPresentation.cs` | ??ぉ??5媛??뚮떎 ?몃뱾????ConversationPanel???⑥씪 ?꾩엫 ?몃뱾??(Tag 湲곕컲 遺꾧린). ???꾪솚 ??250媛??몃뱾???꾩쟻 ?댁냼 | +| TopicPreset ?대깽???꾩엫 | `ChatWindow.TopicPresetPresentation.cs` | 移대뱶??3媛??뚮떎 ?몃뱾????TopicButtonPanel???⑥씪 ?꾩엫 ?몃뱾?? ???꾪솚 ??45媛??몃뱾???꾩쟻 ?댁냼 | +| 怨듯넻 VisualTree ?ы띁 | `ChatWindow.VisualInteractionHelpers.cs` | `FindAncestorWithTag`, `FindAncestor` ?좏떥 異붽? | -### 구조적 리팩토링 P2 (2026-04-09) +### 援ъ“??由ы뙥?좊쭅 P2 (2026-04-09) -| 대상 | 파일 | 변경 | +| ?€??| ?뚯씪 | 蹂€寃?| |------|------|------| -| _agentLiveContainer 인크리멘탈 허용 | `TranscriptRenderPlanner.cs`, `TranscriptRenderExecution.cs` | 라이브 컨테이너를 expectedChildCount에 포함, 인크리멘탈 시 임시 분리/재삽입 → `hasExternalChildren` 차단 해소 | -| 스트리밍 append-only 렌더 | `TranscriptRenderExecution.cs`, `TranscriptRendering.cs` | prefix 비교 우회하는 `TryApplyStreamingAppendRender` 추가 — stable 키 부분집합 관계만 확인, 새 항목만 추가 | -| Permission 이벤트 위임 | `ChatWindow.PermissionPresentation.cs` | 행당 4개 람다 → PermissionItems에 단일 위임 핸들러 + `PermissionItemTag` | -| Preview 탭 이벤트 위임 | `ChatWindow.PreviewPresentation.cs` | 탭당 7개 람다 → PreviewTabPanel에 단일 위임 핸들러 + `PreviewTabTag` | -| GitBranch 이벤트 위임 | `ChatWindow.GitBranchPresentation.cs`, `SelectionPopupPresentation.cs` | `CreateFlatPopupRow`/`CreatePopupMenuRow` 행의 람다 → GitBranchItems에 단일 위임 + `PopupRowTag` | +| _agentLiveContainer ?명겕由щ찘???덉슜 | `TranscriptRenderPlanner.cs`, `TranscriptRenderExecution.cs` | ?쇱씠釉?而⑦뀒?대꼫瑜?expectedChildCount???ы븿, ?명겕由щ찘?????꾩떆 遺꾨━/?ъ궫????`hasExternalChildren` 李⑤떒 ?댁냼 | +| ?ㅽ듃由щ컢 append-only ?뚮뜑 | `TranscriptRenderExecution.cs`, `TranscriptRendering.cs` | prefix 鍮꾧탳 ?고쉶?섎뒗 `TryApplyStreamingAppendRender` 異붽? ??stable ??遺€遺꾩쭛??愿€怨꾨쭔 ?뺤씤, ????ぉ留?異붽? | +| Permission ?대깽???꾩엫 | `ChatWindow.PermissionPresentation.cs` | ?됰떦 4媛??뚮떎 ??PermissionItems???⑥씪 ?꾩엫 ?몃뱾??+ `PermissionItemTag` | +| Preview ???대깽???꾩엫 | `ChatWindow.PreviewPresentation.cs` | ??떦 7媛??뚮떎 ??PreviewTabPanel???⑥씪 ?꾩엫 ?몃뱾??+ `PreviewTabTag` | +| GitBranch ?대깽???꾩엫 | `ChatWindow.GitBranchPresentation.cs`, `SelectionPopupPresentation.cs` | `CreateFlatPopupRow`/`CreatePopupMenuRow` ?됱쓽 ?뚮떎 ??GitBranchItems???⑥씪 ?꾩엫 + `PopupRowTag` | -### 구조적 리팩토링 P3 (2026-04-09) +### 援ъ“??由ы뙥?좊쭅 P3 (2026-04-09) -| 대상 | 파일 | 변경 | +| ?€??| ?뚯씪 | 蹂€寃?| |------|------|------| -| FileBrowser 명시적 해제 | `ChatWindow.FileBrowserPresentation.cs` | TreeViewItem 람다→명명 메서드(`FileTreeItem_Expanded/DoubleClick/RightClick`) 전환. `BuildFileTree()` 시 `DetachFileTreeHandlers()` 재귀 호출로 Clear 전 핸들러 해제. 트리 재구축당 300개 핸들러 누적 해소 | +| FileBrowser 紐낆떆???댁젣 | `ChatWindow.FileBrowserPresentation.cs` | TreeViewItem ?뚮떎?믩챸紐?硫붿꽌??`FileTreeItem_Expanded/DoubleClick/RightClick`) ?꾪솚. `BuildFileTree()` ??`DetachFileTreeHandlers()` ?ш? ?몄텧濡?Clear ???몃뱾???댁젣. ?몃━ ?ш뎄異뺣떦 300媛??몃뱾???꾩쟻 ?댁냼 | -> 전체 계획 완료. `docs/STRUCTURAL_REFACTORING_PLAN.md` 참조. -### 런처 · 에이전트 리소스/안정성 수정 (2026-04-09) +## claude-code식 transcript 표시 구조 정리 -| 대상 | 파일 | 변경 | -|------|------|------| -| LauncherWindow 이벤트 누수 | `LauncherWindow.xaml.cs` | `vm.CloseRequested`, `vm.PropertyChanged`, `app.IndexService.IndexRebuilt` 핸들러를 필드 저장 → `OnClosed`에서 `-=` 해제. ViewModel보다 Window가 먼저 닫힐 때 GC 누수 방지 | -| ChatWindow 타이머 정리 | `ChatWindow.xaml.cs` | `Closed` 핸들러에 누락된 8개 타이머 명시적 `Stop()` 추가 + `StopAgentEventProcessor()` 호출 | -| Events 스레드 안전 | `AgentLoopService.cs` | Dispatcher 없을 때 `Events` 접근에 `lock(Events)` 추가 — 동시 EmitEvent 호출 시 IndexOutOfRange 크래시 방지 | -| NotifyTool 타이머 누적 | `NotifyTool.cs` | 알림당 `new DispatcherTimer` → `DoubleAnimation.Completed` 콜백으로 대체. 100개 알림 시 100개 타이머 동시 존재 해소 | -| LauncherWindow 토스트 타이머 | `LauncherWindow.xaml.cs` | `ShowToast()` 매 호출 `new DispatcherTimer` → 재사용 패턴 + 명명 메서드(`ToastTimer_Tick`) | -| LauncherWindow 타이머 정리 | `LauncherWindow.xaml.cs` | `OnClosed`에 `_toastTimer?.Stop()`, `_indexStatusTimer?.Stop()` 추가 | +- 업데이트: 2026-04-09 13:05 (KST) +- AgentTranscriptDisplayCatalog를 row presentation 중심으로 재구성해 hinking / waiting / compact / tool activity / permission / tool result / status를 개별 transcript row 의미로 다룰 수 있게 정리했습니다. +- ChatWindow.AgentEventRendering은 process feed 계열 이벤트를 같은 GroupKey 단위로 병합해, 긴 Cowork/Code 실행 중 append 수를 줄이면서도 주요 활동 흐름이 기본 transcript에 남도록 조정했습니다. +- PermissionRequestPresentationCatalog, ToolResultPresentationCatalog를 다시 정리해 권한 요청과 도구 결과를 행위/상태 기준으로 구분하고, transcript 렌더와 popup이 같은 메타를 공유하도록 맞췄습니다. +- ChatWindow.FooterPresentation은 execution event가 생긴 뒤에는 프리셋 안내 카드를 자동으로 숨기도록 바꿔 결과/진행 화면을 덮지 않게 했고, Cowork/Code 입력 워터마크와 footer 기본 문구도 정상 한국어 기준으로 다시 정리했습니다. +- +ender_messages 성능 로그에는 processFeedAppends, processFeedMerges, +owKindCounts를 함께 남겨 %APPDATA%\\AxCopilot\\perf 기준으로 transcript grouping 효과를 수치로 비교할 수 있게 했습니다. -### Hot path · 리소스 추가 최적화 (2026-04-09) +## 13. ?붾젆?좊━蹂?媛€?대뱶 -| 대상 | 파일 | 변경 | -|------|------|------| -| GetRuntimeActiveTools 캐시 | `AgentLoopService.cs` | 반복당 1~4회 호출 → `cachedActiveTools` 로컬 변수로 1회 캐시. foreach 내 `activeToolNames` 계산도 루프 밖으로 호이스트 | -| SubAgentTool 취소 전파 | `SubAgentTool.cs` | `CancellationTokenSource.CreateLinkedTokenSource(ct)` 연동. Task.Run + loop.RunAsync에 토큰 전달. 부모 중지 시 자식 즉시 취소 | -| 아이콘 애니메이션 재귀 제어 | `LauncherWindow.xaml.cs` | `sb.Completed`에서 즉시 재귀 → `_iconAnimationDelayTimer` 8초 딜레이. 할당 빈도 75% 감소. 클릭 시 딜레이 취소 후 즉시 전환 | -| JsonSerializerOptions 공유 | `AgentLoopService.cs` | `s_jsonOpts` 정적 필드 추가, 4개 `JsonSerializer.Serialize` 호출에 적용. L4096 `System.Text.Json.` 접두사 정규화 | - -### Claude Desktop 스타일 UI 개선 (2026-04-09) - -| 항목 | 파일 | 수정 내용 | -|------|------|----------| -| 미리보기 Split Button | `ChatWindow.xaml` | 기존 `BtnPreviewToggle` (Ellipse 점 + "프리뷰") → `[▶ 미리보기 | ∨]` Split Button으로 교체. 좌측 토글, 우측 셰브론 드롭다운 | -| 미리보기 드롭다운 | `ChatWindow.PreviewPresentation.cs` | `ShowPreviewTabDropdown()` — 열린 탭 목록 팝업, 파일 확장자별 아이콘, 활성 탭 하이라이트 | -| PreviewDot → PreviewIcon | `ChatWindow.PreviewPresentation.cs` | `PreviewDot.Fill` 4곳 → `PreviewIcon.Foreground` (AccentColor/SecondaryText) 전환 | -| 셰브론 동기화 | `ChatWindow.PreviewPresentation.cs` | `UpdatePreviewChevronState()` — `_previewTabs.Count` 기반 IsHitTestVisible/Opacity 제어 | -| 계획 버튼 이동 | `ChatWindow.xaml` | MoodIconPanel 동적 주입 → StatusBar XAML 선언 요소 `BtnPlanViewer`로 이동 | -| ShowPlanButton 리팩토링 | `ChatWindow.PlanApprovalPresentation.cs` | 동적 Add/Remove → `Visibility` 토글 단순화 + 레거시 정리 유지 | - -### 에이전트 루프 문서 생성 흐름 수정 (2026-04-09) - -| 파일 | 수정 내용 | -|------|----------| -| `AgentLoopTransitions.Documents.cs` | `TryHandleTerminalDocumentCompletionTransitionAsync`에서 `document_plan` 없이 바로 문서 도구 호출 시 조기 종료 방지 — LLM이 추가 반복으로 내용을 보강할 수 있도록 허용 | -| `HtmlSkill.cs` | `MarkdownToHtml`에서 LLM이 삽입한 `
` 태그가 이스케이프되는 버그 수정 — 이스케이프 전 플레이스홀더로 보존 후 복원 | - ---- - -## 13. 디렉토리별 가이드 - -| 디렉토리 | 수정 시 주의사항 | +| ?붾젆?좊━ | ?섏젙 ??二쇱쓽?ы빆 | |---------|----------------| -| `Core/` | `FuzzyEngine` 점수 공식 변경 시 검색 품질에 직접 영향 | -| `Handlers/` | 새 핸들러 추가 시 `App.xaml.cs`에 등록 필요 | -| `Services/Agent/` | 새 도구 추가 시 `ToolRegistry`에 등록 + 스킬 파일(`.skill.md`) 작성 | -| `Themes/` | 리소스 키 변경 시 모든 테마에 동일하게 적용 필요 | -| `Models/AppSettings.cs` | 속성 추가 시 `SettingsService` 마이그레이션 고려 | -| `Views/ChatWindow.*` | partial class 분할 — 관련 기능은 해당 파일에서 수정 | +| `Core/` | `FuzzyEngine` ?먯닔 怨듭떇 蹂€寃???寃€???덉쭏??吏곸젒 ?곹뼢 | +| `Handlers/` | ???몃뱾??異붽? ??`App.xaml.cs`???깅줉 ?꾩슂 | +| `Services/Agent/` | ???꾧뎄 異붽? ??`ToolRegistry`???깅줉 + ?ㅽ궗 ?뚯씪(`.skill.md`) ?묒꽦 | +| `Themes/` | 由ъ냼????蹂€寃???紐⑤뱺 ?뚮쭏???숈씪?섍쾶 ?곸슜 ?꾩슂 | +| `Models/AppSettings.cs` | ?띿꽦 異붽? ??`SettingsService` 留덉씠洹몃젅?댁뀡 怨좊젮 | +| `Views/ChatWindow.*` | partial class 遺꾪븷 ??愿€??湲곕뒫?€ ?대떦 ?뚯씪?먯꽌 ?섏젙 | --- -## 14. 관련 문서 +## 14. 愿€??臾몄꽌 -| 문서 | 내용 | +| 臾몄꽌 | ?댁슜 | |------|------| -| `docs/AGENT_ROADMAP.md` | 에이전트 기능 로드맵 | -| `docs/LAUNCHER_ROADMAP.md` | 런처 기능 로드맵 | -| `docs/CLAW_CODE_PARITY_PLAN.md` | Claude Code 기능 대응 계획 | -| `docs/TOOL_PARITY_REPORT.md` | 도구 호환성 리포트 | -| `docs/AX_AGENT_UI_CHECKLIST.md` | 에이전트 UI 체크리스트 | -| `docs/UI_UX_CHECKLIST.md` | UI/UX 체크리스트 | +| `docs/AGENT_ROADMAP.md` | ?먯씠?꾪듃 湲곕뒫 濡쒕뱶留?| +| `docs/LAUNCHER_ROADMAP.md` | ?곗쿂 湲곕뒫 濡쒕뱶留?| +| `docs/CLAW_CODE_PARITY_PLAN.md` | Claude Code 湲곕뒫 ?€??怨꾪쉷 | +| `docs/TOOL_PARITY_REPORT.md` | ?꾧뎄 ?명솚??由ы룷??| +| `docs/AX_AGENT_UI_CHECKLIST.md` | ?먯씠?꾪듃 UI 泥댄겕由ъ뒪??| +| `docs/UI_UX_CHECKLIST.md` | UI/UX 泥댄겕由ъ뒪??| --- -### 선택적 탐색 구조 개선 (2026-04-09 10:36 KST) +### ?좏깮???먯깋 援ъ“ 媛쒖꽑 (2026-04-09 10:36 KST) -- `claude-code`의 `Glob/Grep/FileRead` 프롬프트와 `toolOrchestration.ts` 흐름을 다시 대조한 결과, AX는 `folder_map`을 너무 쉽게 먼저 호출하도록 유도하는 규칙 때문에 질문과 무관한 전체 워크스페이스를 훑는 경향이 있었습니다. +- `claude-code`??`Glob/Grep/FileRead` ?꾨\?꾪듃?€ `toolOrchestration.ts` ?먮쫫???ㅼ떆 ?€議고븳 寃곌낵, AX??`folder_map`???덈Т ?쎄쾶 癒쇱? ?몄텧?섎룄濡??좊룄?섎뒗 洹쒖튃 ?뚮Ц??吏덈Ц怨?臾닿????꾩껜 ?뚰겕?ㅽ럹?댁뒪瑜??묐뒗 寃쏀뼢???덉뿀?듬땲?? - `src/AxCopilot/Views/ChatWindow.xaml.cs` - - Cowork/Code 시스템 프롬프트에서 `folder_map`을 항상 첫 단계로 요구하던 문구를 완화했습니다. - - 좁은 범위의 질문은 `glob/grep + targeted file_read`를 우선하고, 저장소 전체 구조가 정말 필요할 때만 `folder_map`을 쓰도록 바꿨습니다. + - Cowork/Code ?쒖뒪???꾨\?꾪듃?먯꽌 `folder_map`????긽 泥??④퀎濡??붽뎄?섎뜕 臾멸뎄瑜??꾪솕?덉뒿?덈떎. + - 醫곸? 踰붿쐞??吏덈Ц?€ `glob/grep + targeted file_read`瑜??곗꽑?섍퀬, ?€?μ냼 ?꾩껜 援ъ“媛€ ?뺣쭚 ?꾩슂???뚮쭔 `folder_map`???곕룄濡?諛붽엥?듬땲?? - `src/AxCopilot/Services/Agent/FolderMapTool.cs` - - 기본 탐색 depth를 `3 -> 2`로 낮췄습니다. - - `include_files` 기본값을 `true -> false`로 바꿔 첫 패스에서 구조 확인 위주로 동작하게 했습니다. -- `src/AxCopilot/Services/Agent/MultiReadTool.cs` - - 한 번에 읽을 수 있는 최대 파일 수를 `20 -> 8`로 낮춰 초기 과탐색과 토큰 낭비를 줄였습니다. -- `src/AxCopilot/Services/Agent/AgentLoopExplorationPolicy.cs` - - 새 partial 파일을 추가해 탐색 범위를 `Localized / TopicBased / RepoWide / OpenEnded`로 분류하는 정책을 도입했습니다. - - `folder_map` 반복, 대량 `multi_read`, broad scan 패턴이 나오면 관련 파일만 다시 고르도록 corrective hint를 주입하는 규칙을 넣었습니다. -- `src/AxCopilot/Services/Agent/AgentLoopService.cs` - - 루프 시작 시 탐색 범위 가이드를 시스템 메시지로 주입합니다. - - 도구 실행 중 `folder_map` 호출 수, `multi_read` 파일 수, 총 읽은 파일 수를 추적합니다. - - 좁은 범위 질문에서 broad scan이 감지되면 `탐색 범위를 좁히는 중 · 관련 파일만 다시 선택합니다` 진행 메시지를 남기고 선택적 탐색으로 되돌립니다. -- `src/AxCopilot/Services/AgentPerformanceLogService.cs` - - `%APPDATA%\\AxCopilot\\perf`에 `exploration_breadth` 성능 로그를 남겨 broad scan 여부와 selective hit 여부를 실사용 기준으로 검증할 수 있게 했습니다. + - 湲곕낯 ?먯깋 depth瑜?`3 - +## claude-code식 transcript 표시 구조 정리 + +- 업데이트: 2026-04-09 13:05 (KST) +- AgentTranscriptDisplayCatalog를 row presentation 중심으로 재구성해 hinking / waiting / compact / tool activity / permission / tool result / status를 개별 transcript row 의미로 다룰 수 있게 정리했습니다. +- ChatWindow.AgentEventRendering은 process feed 계열 이벤트를 같은 GroupKey 단위로 병합해, 긴 Cowork/Code 실행 중 append 수를 줄이면서도 주요 활동 흐름이 기본 transcript에 남도록 조정했습니다. +- PermissionRequestPresentationCatalog, ToolResultPresentationCatalog를 다시 정리해 권한 요청과 도구 결과를 행위/상태 기준으로 구분하고, transcript 렌더와 popup이 같은 메타를 공유하도록 맞췄습니다. +- ChatWindow.FooterPresentation은 execution event가 생긴 뒤에는 프리셋 안내 카드를 자동으로 숨기도록 바꿔 결과/진행 화면을 덮지 않게 했고, Cowork/Code 입력 워터마크와 footer 기본 문구도 정상 한국어 기준으로 다시 정리했습니다. +- +ender_messages 성능 로그에는 processFeedAppends, processFeedMerges, +owKindCounts를 함께 남겨 %APPDATA%\\AxCopilot\\perf 기준으로 transcript grouping 효과를 수치로 비교할 수 있게 했습니다. + +### transcript row 怨꾩빟 諛?activity grouping ?뺣━ (2026-04-09 11:12 KST) + +- `claude-code`??`Messages.tsx`, `MessageRow.tsx`, `GroupedToolUseContent.tsx`, `UserToolResultMessage`, `PermissionRequest`瑜??ㅼ떆 ?€議고빐 AX transcript瑜?row ?€??以묒떖?쇰줈 ?뺣━?덉뒿?덈떎. +- `src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs` + - `TranscriptRowKind`, `AgentTranscriptRowPresentation`??異붽??덉뒿?덈떎. + - thinking / waiting / compact / tool activity / permission / tool result / status瑜??섎굹??移댄깉濡쒓렇?먯꽌 ?뺢퇋?뷀븯?꾨줉 諛붽엥?듬땲?? + - process feed row??`GroupKey`, `CanGroup`, `Emphasize` 硫뷀?瑜??④퍡 怨꾩궛???뚮뜑?ш? 怨좊퉰???대깽?몃? ???곸? ?됱쑝濡?臾띠쓣 ???덇쾶 ?덉뒿?덈떎. +- `src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs` + - process feed ?뚮뜑媛€ row presentation??吏곸젒 諛쏆븘 ?쒕ぉ/?ㅻ챸/媛뺤“ ?щ?瑜?援ъ꽦?섎룄濡?諛붽엥?듬땲?? + - 媛숈? 醫낅쪟??read/search/step ?대깽?몃뒗 留덉?留?grouped row瑜?援먯껜?섎뒗 諛⑹떇?쇰줈 merge?섏뿬 append ?섎? 以꾩??듬땲?? + - permission/result card??row 硫뷀??€ presentation catalog瑜???吏곸젒?곸쑝濡??ъ슜?섎룄濡??곌껐?덉뒿?덈떎. +- `src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs` + - ?깃났/?ㅽ뙣/嫄곕?/痍⑥냼/?뱀씤 ?꾩슂/遺€遺??꾨즺瑜?clean??硫뷀? 援ъ“濡??ㅼ떆 ?뺣━?덉뒿?덈떎. +- `src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs` + - bash / powershell / command / web / mcp / skill / ask / file edit / file write / git / document / filesystem???됱쐞蹂?沅뚰븳 移대뱶 硫뷀?濡??ъ젙?섑뻽?듬땲?? +- `src/AxCopilot/Views/ChatWindow.TranscriptHost.cs` + - transcript 珥덇린????grouped process feed ?곹깭?€ 移댁슫?곕룄 ?④퍡 由ъ뀑?섎룄濡?蹂닿컯?덉뒿?덈떎. +- `src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs` + - performance log detail??`processFeedAppends`, `processFeedMerges`瑜?異붽???grouped activity row???④낵瑜??ㅼ궗??濡쒓렇?먯꽌 ?뺤씤?????덇쾶 ?덉뒿?덈떎. + diff --git a/src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs b/src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs index 159ed31..7c8ba4e 100644 --- a/src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs +++ b/src/AxCopilot/Services/Agent/AgentTranscriptDisplayCatalog.cs @@ -2,6 +2,28 @@ using System; namespace AxCopilot.Services.Agent; +internal enum TranscriptRowKind +{ + AssistantText, + Thinking, + ToolActivity, + Permission, + ToolResult, + CompactBoundary, + Waiting, + PlanApproval, + Status, +} + +internal sealed record AgentTranscriptRowPresentation( + TranscriptRowKind Kind, + string BadgeLabel, + string Title, + string Description, + string GroupKey, + bool CanGroup, + bool Emphasize); + internal static class AgentTranscriptDisplayCatalog { public static string GetDisplayName(string? rawName, bool slashPrefix = false) @@ -15,20 +37,27 @@ internal static class AgentTranscriptDisplayCatalog { "file_read" => "파일 읽기", "file_write" => "파일 쓰기", - "file_edit" => "파일 편집", - "file_watch" => "파일 변경 감시", + "file_edit" => "파일 수정", + "file_watch" => "파일 감시", "file_info" => "파일 정보", "file_manage" => "파일 관리", "glob" => "파일 찾기", "grep" => "내용 검색", "folder_map" => "폴더 구조", + "multi_read" => "다중 파일 읽기", - "document_reader" => "문서 읽기", - "document_planner" => "문서 계획", - "document_assembler" => "문서 조합", + "document_read" or "document_reader" => "문서 읽기", + "document_plan" or "document_planner" => "문서 계획", + "document_assemble" or "document_assembler" => "문서 조합", "document_review" => "문서 검토", "format_convert" => "형식 변환", "template_render" => "템플릿 렌더", + "html_create" => "HTML 생성", + "docx_create" => "Word 생성", + "markdown_create" or "md_create" => "Markdown 생성", + "excel_create" or "xlsx_create" => "Excel 생성", + "csv_create" => "CSV 생성", + "pptx_create" => "PowerPoint 생성", "build_run" => "빌드/실행", "test_loop" => "테스트 루프", @@ -42,11 +71,11 @@ internal static class AgentTranscriptDisplayCatalog "powershell" => "PowerShell", "web_fetch" => "웹 요청", "http" => "HTTP 요청", - "user_ask" => "의견 요청", + "user_ask" => "질문 요청", "suggest_actions" => "다음 작업 제안", "task_create" => "작업 생성", - "task_update" => "작업 업데이트", + "task_update" => "작업 갱신", "task_list" => "작업 목록", "task_get" => "작업 조회", "task_stop" => "작업 중지", @@ -104,7 +133,7 @@ internal static class AgentTranscriptDisplayCatalog AgentEventType.ToolResult => evt.Success ? $"{displayName} 실행 완료" : $"{displayName} 실행 실패", AgentEventType.SkillCall => $"{displayName} 실행", AgentEventType.PermissionRequest => $"{displayName} 실행 전에 권한 확인이 필요합니다.", - AgentEventType.PermissionGranted => $"{displayName} 실행 권한이 승인되었습니다.", + AgentEventType.PermissionGranted => $"{displayName} 실행 권한을 확인했습니다.", AgentEventType.PermissionDenied => $"{displayName} 실행 권한이 거부되었습니다.", AgentEventType.Complete => "에이전트 작업이 완료되었습니다.", AgentEventType.Error => "에이전트 실행 중 오류가 발생했습니다.", @@ -112,6 +141,257 @@ internal static class AgentTranscriptDisplayCatalog }; } + public static AgentTranscriptRowPresentation ResolveRowPresentation(AgentEvent evt, string itemDisplayName, string transcriptBadgeLabel) + { + var summary = (evt.Summary ?? string.Empty).Trim(); + var toolName = (evt.ToolName ?? string.Empty).Trim().ToLowerInvariant(); + var resultPresentation = evt.Type == AgentEventType.ToolResult + ? ToolResultPresentationCatalog.Resolve(evt, transcriptBadgeLabel) + : null; + var permissionPresentation = evt.Type switch + { + AgentEventType.PermissionRequest => PermissionRequestPresentationCatalog.Resolve(evt.ToolName, pending: true), + AgentEventType.PermissionGranted => PermissionRequestPresentationCatalog.Resolve(evt.ToolName, pending: false), + _ => null + }; + + if (string.Equals(toolName, "agent_wait", StringComparison.OrdinalIgnoreCase)) + { + return new AgentTranscriptRowPresentation( + TranscriptRowKind.Waiting, + "대기", + "처리 중...", + string.IsNullOrWhiteSpace(summary) ? "작업을 계속 진행하기 위한 응답을 기다리는 중입니다." : summary, + "waiting:agent", + true, + true); + } + + if (string.Equals(toolName, "context_compaction", StringComparison.OrdinalIgnoreCase)) + { + return new AgentTranscriptRowPresentation( + TranscriptRowKind.CompactBoundary, + "압축", + "컨텍스트 압축 중...", + string.IsNullOrWhiteSpace(summary) ? "긴 대화를 계속 진행하기 위해 컨텍스트를 정리하고 있습니다." : summary, + "compact:context", + true, + true); + } + + if (evt.Type == AgentEventType.Planning) + { + var title = evt.Steps is { Count: > 0 } + ? $"계획 {evt.Steps.Count}단계 정리" + : "작업 계획 정리 중"; + return new AgentTranscriptRowPresentation( + TranscriptRowKind.Thinking, + "계획", + title, + string.IsNullOrWhiteSpace(summary) ? "실행 순서와 필요한 도구를 정리하고 있습니다." : summary, + "planning", + true, + false); + } + + if (evt.Type == AgentEventType.Decision) + { + return new AgentTranscriptRowPresentation( + TranscriptRowKind.PlanApproval, + "확인", + "계획 확인 필요", + string.IsNullOrWhiteSpace(summary) ? "사용자 확인이 필요한 계획 단계입니다." : summary, + "plan:approval", + false, + true); + } + + if (evt.Type is AgentEventType.StepStart or AgentEventType.StepDone) + { + var title = evt.StepTotal > 0 + ? evt.Type == AgentEventType.StepStart + ? $"{evt.StepCurrent}/{evt.StepTotal} 단계 진행" + : $"{evt.StepCurrent}/{evt.StepTotal} 단계 완료" + : evt.Type == AgentEventType.StepStart ? "단계 진행" : "단계 완료"; + return new AgentTranscriptRowPresentation( + TranscriptRowKind.ToolActivity, + "단계", + title, + string.IsNullOrWhiteSpace(summary) ? title : summary, + $"step:{evt.StepCurrent}:{evt.StepTotal}:{evt.Type}", + false, + false); + } + + if (evt.Type == AgentEventType.Thinking) + { + var title = ResolveThinkingTitle(summary, toolName); + return new AgentTranscriptRowPresentation( + TranscriptRowKind.Thinking, + "생각", + title, + string.IsNullOrWhiteSpace(summary) ? title : summary, + $"thinking:{ResolveActivityGroup(toolName, summary)}", + true, + false); + } + + if (evt.Type == AgentEventType.ToolCall || evt.Type == AgentEventType.SkillCall) + { + var group = ResolveActivityGroup(toolName, summary); + var title = BuildActivityTitle(toolName, itemDisplayName, summary); + var badge = evt.Type == AgentEventType.SkillCall ? "스킬" : "도구"; + return new AgentTranscriptRowPresentation( + TranscriptRowKind.ToolActivity, + badge, + title, + BuildActivityDescription(group, summary), + $"activity:{group}", + true, + false); + } + + if (permissionPresentation != null) + { + return new AgentTranscriptRowPresentation( + TranscriptRowKind.Permission, + "권한", + permissionPresentation.Label, + permissionPresentation.Description, + $"permission:{permissionPresentation.Kind}", + false, + true); + } + + if (resultPresentation != null) + { + return new AgentTranscriptRowPresentation( + TranscriptRowKind.ToolResult, + "결과", + resultPresentation.Label, + resultPresentation.Description, + $"result:{resultPresentation.Kind}:{resultPresentation.StatusKind}", + false, + resultPresentation.NeedsAttention); + } + + if (evt.Type == AgentEventType.Error) + { + return new AgentTranscriptRowPresentation( + TranscriptRowKind.Status, + "오류", + "실행 중 오류 발생", + string.IsNullOrWhiteSpace(summary) ? "에이전트 실행 중 오류가 발생했습니다." : summary, + "status:error", + false, + true); + } + + if (evt.Type == AgentEventType.Complete) + { + return new AgentTranscriptRowPresentation( + TranscriptRowKind.Status, + "완료", + "작업 완료", + string.IsNullOrWhiteSpace(summary) ? "에이전트 작업이 완료되었습니다." : summary, + "status:complete", + false, + false); + } + + return new AgentTranscriptRowPresentation( + TranscriptRowKind.Status, + transcriptBadgeLabel, + string.IsNullOrWhiteSpace(summary) ? transcriptBadgeLabel : summary, + string.IsNullOrWhiteSpace(summary) ? transcriptBadgeLabel : summary, + $"status:{evt.Type}", + false, + false); + } + + private static string ResolveThinkingTitle(string summary, string toolName) + { + if (summary.Contains("검증", StringComparison.OrdinalIgnoreCase)) + return "결과 검증 중..."; + if (summary.Contains("diff", StringComparison.OrdinalIgnoreCase)) + return "변경 내용 확인 중..."; + if (summary.Contains("retry", StringComparison.OrdinalIgnoreCase) || summary.Contains("재시도", StringComparison.OrdinalIgnoreCase)) + return "재시도 준비 중..."; + if (summary.Contains("fallback", StringComparison.OrdinalIgnoreCase) || summary.Contains("자동 생성", StringComparison.OrdinalIgnoreCase)) + return "대체 경로 준비 중..."; + if (toolName.Contains("document")) + return "문서 흐름 정리 중..."; + return string.IsNullOrWhiteSpace(summary) ? "분석 중..." : summary; + } + + private static string ResolveActivityGroup(string toolName, string summary) + { + if (toolName is "file_read" or "document_read" or "glob" or "grep" or "folder_map" or "multi_read") + return "read"; + if (toolName is "file_edit" or "file_write") + return "edit"; + if (toolName.Contains("build") || toolName.Contains("test") || toolName == "process") + return "execute"; + if (toolName.Contains("git") || toolName.Contains("diff")) + return "git"; + if (toolName.Contains("document") || toolName.Contains("html_create") || toolName.Contains("docx_create") || toolName.Contains("markdown_create")) + return "document"; + if (toolName.Contains("web") || toolName.Contains("http")) + return "web"; + if (summary.Contains("권한", StringComparison.OrdinalIgnoreCase)) + return "permission"; + return "general"; + } + + private static string BuildActivityTitle(string toolName, string itemDisplayName, string summary) + { + if (toolName is "multi_read") + return "여러 파일 읽는 중"; + if (toolName is "file_read" or "document_read") + return "파일 읽는 중"; + if (toolName is "glob") + return "관련 파일 찾는 중"; + if (toolName is "grep") + return "코드 검색 중"; + if (toolName is "folder_map") + return "구조 확인 중"; + if (toolName is "file_edit") + return "파일 수정 중"; + if (toolName is "file_write") + return "파일 작성 중"; + if (toolName.Contains("build") || toolName.Contains("test")) + return "빌드/테스트 실행 중"; + if (toolName == "process" || toolName == "bash" || toolName == "powershell") + return "명령 실행 중"; + if (toolName.Contains("git") || toolName.Contains("diff")) + return "Git 작업 중"; + if (toolName.Contains("document") || toolName.Contains("html_create") || toolName.Contains("docx_create")) + return "문서 결과 만드는 중"; + if (toolName.Contains("web") || toolName.Contains("http")) + return "웹 정보 확인 중"; + + if (!string.IsNullOrWhiteSpace(itemDisplayName)) + return $"{itemDisplayName} 실행 중"; + return string.IsNullOrWhiteSpace(summary) ? "도구 실행 중" : summary; + } + + private static string BuildActivityDescription(string group, string summary) + { + if (!string.IsNullOrWhiteSpace(summary)) + return summary; + + return group switch + { + "read" => "질문과 관련된 파일과 내용만 추려서 확인하고 있습니다.", + "edit" => "필요한 변경만 적용하고 결과를 다시 확인하고 있습니다.", + "execute" => "실행 결과와 로그를 확인해 다음 단계를 판단하고 있습니다.", + "git" => "변경 범위와 저장소 상태를 확인하고 있습니다.", + "document" => "문서 산출물을 준비하고 있습니다.", + "web" => "필요한 외부 정보를 확인하고 있습니다.", + _ => "다음 단계를 진행하기 위한 작업을 실행하고 있습니다." + }; + } + private static string GetToolCategoryLabel(string? rawName) { if (string.IsNullOrWhiteSpace(rawName)) @@ -119,13 +399,13 @@ internal static class AgentTranscriptDisplayCatalog return rawName.Trim().ToLowerInvariant() switch { - "file_read" or "file_write" or "file_edit" or "glob" or "grep" or "folder_map" or "file_watch" or "file_info" or "file_manage" + "file_read" or "file_write" or "file_edit" or "glob" or "grep" or "folder_map" or "file_watch" or "file_info" or "file_manage" or "multi_read" => "파일", "build_run" or "test_loop" or "dev_env_detect" => "빌드", "git_tool" or "diff_tool" or "diff_preview" => "Git", - "document_reader" or "document_planner" or "document_assembler" or "document_review" or "format_convert" or "template_render" + "document_read" or "document_reader" or "document_plan" or "document_planner" or "document_assemble" or "document_assembler" or "document_review" or "format_convert" or "template_render" or "html_create" or "docx_create" => "문서", "user_ask" => "질문", diff --git a/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs b/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs index dd84b9e..70ce514 100644 --- a/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs +++ b/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs @@ -1,3 +1,5 @@ +using System; + namespace AxCopilot.Services.Agent; internal sealed record PermissionRequestPresentation( @@ -20,104 +22,104 @@ internal static class PermissionRequestPresentationCatalog if (tool.Contains("bash")) return Build("bash", pending, "\uE756", "Bash 실행 권한 요청", "Bash 실행 확인", - "셸 명령을 실행하기 전에 확인이 필요합니다.", - "Bash 실행이 승인되어 계속 진행합니다.", - "명령과 작업 위치를 확인하세요.", + "Bash 명령을 실행하기 전에 사용자 확인이 필요합니다.", + "Bash 실행을 확인해 주시면 같은 흐름으로 이어집니다.", + "명령과 작업 위치를 먼저 확인해 주세요.", "#FEF2F2", "#DC2626", "high", false); if (tool.Contains("powershell")) return Build("powershell", pending, "\uE756", "PowerShell 권한 요청", "PowerShell 실행 확인", - "PowerShell 명령을 실행하기 전에 확인이 필요합니다.", - "PowerShell 실행이 승인되어 계속 진행합니다.", - "스크립트와 실행 범위를 확인하세요.", + "PowerShell 명령을 실행하기 전에 사용자 확인이 필요합니다.", + "PowerShell 실행을 승인하면 같은 작업을 이어서 진행합니다.", + "스크립트와 실행 범위를 확인해 주세요.", "#FEF2F2", "#DC2626", "high", false); if (tool.Contains("process") || tool.Contains("build") || tool.Contains("test")) return Build("command", pending, "\uE756", "명령 실행 권한 요청", "명령 실행 확인", - "명령 또는 빌드 작업 실행 전에 확인이 필요합니다.", - "명령 실행이 승인되어 계속 진행합니다.", - "실행 명령과 영향 범위를 확인하세요.", + "명령이나 빌드 작업을 실행하기 전에 확인이 필요합니다.", + "명령 실행을 승인하면 현재 흐름을 이어서 진행합니다.", + "실행 명령과 영향 범위를 먼저 확인해 주세요.", "#FEF2F2", "#DC2626", "high", false); if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http")) return Build("web_fetch", pending, "\uE774", "웹 요청 권한 요청", "웹 요청 확인", - "외부 요청을 보내기 전에 확인이 필요합니다.", - "웹 요청이 승인되어 계속 진행합니다.", - "조회 URL과 전송 범위를 확인하세요.", + "외부 웹 요청을 보내기 전에 확인이 필요합니다.", + "요청 대상을 확인해 주시면 이어서 처리합니다.", + "조회 URL과 전송 범위를 확인해 주세요.", "#FFF7ED", "#C2410C", "medium", false); if (tool.Contains("mcp")) return Build("mcp", pending, "\uE943", "MCP 도구 권한 요청", "MCP 도구 확인", - "연결된 MCP 도구 사용 전에 확인이 필요합니다.", - "MCP 도구 사용이 승인되어 계속 진행합니다.", - "서버와 호출 의도를 확인하세요.", + "연결된 MCP 도구를 사용하기 전에 확인이 필요합니다.", + "MCP 도구 사용을 승인하면 흐름을 이어서 진행합니다.", + "서버와 호출 도구를 확인해 주세요.", "#F5F3FF", "#7C3AED", "medium", false); if (tool.Contains("skill")) return Build("skill", pending, "\uE8A5", "스킬 실행 권한 요청", "스킬 실행 확인", "연결된 스킬을 실행하기 전에 확인이 필요합니다.", - "스킬 실행이 승인되어 계속 진행합니다.", - "허용 도구와 실행 컨텍스트를 확인하세요.", + "스킬 실행을 승인하면 같은 작업 흐름을 이어갑니다.", + "사용 도구와 실행 컨텍스트를 확인해 주세요.", "#F5F3FF", "#7C3AED", "medium", false); if (tool.Contains("ask")) return Build("question", pending, "\uE897", - "의견 요청 확인", "의견 요청 완료", - "사용자에게 선택이나 답변을 요청합니다.", - "사용자 답변을 받아 다음 단계로 진행합니다.", - "질문 의도와 선택지를 확인하세요.", + "질문 요청 확인", "질문 요청 완료", + "사용자에게 추가 질문이나 선택을 요청합니다.", + "응답을 받으면 다음 단계로 자동으로 이어집니다.", + "질문 의도와 선택지를 확인해 주세요.", "#EFF6FF", "#2563EB", "low", false); if (tool.Contains("file_edit") || tool.Contains("edit")) return Build("file_edit", pending, "\uE70F", "파일 수정 권한 요청", "파일 수정 확인", - "파일을 변경하기 전에 확인이 필요합니다.", - "파일 수정이 승인되어 계속 진행합니다.", - "변경 diff와 대상 파일을 확인하세요.", + "파일을 변경하기 전에 사용자 확인이 필요합니다.", + "변경 내용을 확인하면 같은 흐름으로 이어집니다.", + "변경 diff와 대상 파일을 먼저 확인해 주세요.", "#FFF7ED", "#C2410C", "high", true); if (tool.Contains("file_write") || tool.Contains("write")) return Build("file_write", pending, "\uE70F", "파일 쓰기 권한 요청", "파일 쓰기 확인", - "새 파일 작성 또는 덮어쓰기 전에 확인이 필요합니다.", - "파일 쓰기가 승인되어 계속 진행합니다.", - "작성 위치와 새 내용 미리보기를 확인하세요.", + "새 파일 생성이나 덮어쓰기 전에 사용자 확인이 필요합니다.", + "파일 쓰기를 승인하면 같은 흐름을 이어갑니다.", + "생성 위치와 내용 미리보기를 확인해 주세요.", "#FFF7ED", "#C2410C", "high", true); if (tool.Contains("git")) return Build("git", pending, "\uE8A7", "Git 작업 권한 요청", "Git 작업 확인", - "브랜치나 커밋 상태를 바꾸기 전에 확인이 필요합니다.", - "Git 작업이 승인되어 계속 진행합니다.", - "브랜치와 변경 범위를 확인하세요.", + "브랜치나 작업트리 상태를 바꾸기 전에 확인이 필요합니다.", + "Git 작업을 승인하면 같은 흐름을 이어갑니다.", + "브랜치와 변경 범위를 확인해 주세요.", "#EFF6FF", "#2563EB", "medium", false); if (tool.Contains("document") || tool.Contains("template") || tool.Contains("format")) return Build("document", pending, "\uE8A5", "문서 작업 권한 요청", "문서 작업 확인", - "문서 생성 또는 변환 작업 전에 확인이 필요합니다.", - "문서 작업이 승인되어 계속 진행합니다.", - "출력 형식과 저장 위치를 확인하세요.", + "문서 생성이나 변환 작업 전에 확인이 필요합니다.", + "문서 작업을 승인하면 같은 흐름을 이어갑니다.", + "출력 형식과 대상 위치를 확인해 주세요.", "#FFF7ED", "#C2410C", "medium", false); if (tool.Contains("file") || tool.Contains("glob") || tool.Contains("grep") || tool.Contains("folder")) return Build("filesystem", pending, "\uE8A5", "파일 접근 권한 요청", "파일 접근 확인", - "폴더나 파일 내용을 읽기 전에 확인이 필요합니다.", - "파일 접근이 승인되어 계속 진행합니다.", - "읽기 범위와 접근 경로를 확인하세요.", + "파일이나 폴더를 읽기 전에 확인이 필요합니다.", + "파일 접근을 승인하면 같은 흐름으로 이어집니다.", + "읽기 범위와 경로를 확인해 주세요.", "#FFF7ED", "#C2410C", "medium", false); return Build("generic", pending, "\uE897", "권한 요청", "권한 확인", - "계속 진행하기 전에 사용자의 확인이 필요합니다.", - "요청이 승인되어 계속 진행합니다.", - "실행 의도와 대상 범위를 확인하세요.", + "계속 진행하기 전에 사용자 확인이 필요합니다.", + "요청을 승인하면 같은 흐름으로 이어집니다.", + "실행 의도와 대상 범위를 확인해 주세요.", "#FFF7ED", "#C2410C", "medium", false); } diff --git a/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs b/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs index 5e1f53d..d0be4fb 100644 --- a/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs +++ b/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs @@ -1,3 +1,5 @@ +using System; + namespace AxCopilot.Services.Agent; internal sealed record ToolResultPresentation( @@ -29,7 +31,7 @@ internal static class ToolResultPresentationCatalog "\uE711", $"{baseLabel} 취소", "요청이 중단되어 결과가 취소되었습니다.", - "필요하면 같은 요청을 다시 실행하세요.", + "필요하면 같은 요청을 다시 실행할 수 있습니다.", "#F8FAFC", "#475569", "cancel", @@ -45,7 +47,7 @@ internal static class ToolResultPresentationCatalog "\uE783", $"{baseLabel} 거부", "권한이 거부되어 작업이 중단되었습니다.", - "권한 모드를 바꾸거나 다시 승인하면 이어서 진행할 수 있습니다.", + "권한 모드를 바꾸거나 다시 확인하면 이어서 진행할 수 있습니다.", "#FEF2F2", "#DC2626", "reject", @@ -58,8 +60,8 @@ internal static class ToolResultPresentationCatalog return new ToolResultPresentation( kind, "\uE8D7", - $"{baseLabel} 승인 대기", - "다음 단계로 진행하려면 사용자 승인이 필요합니다.", + $"{baseLabel} 승인 필요", + "다음 단계로 진행하려면 사용자 승인이나 확인이 필요합니다.", "승인 후 같은 작업 흐름이 이어집니다.", "#FFF7ED", "#C2410C", @@ -74,8 +76,8 @@ internal static class ToolResultPresentationCatalog kind, "\uE7BA", $"{baseLabel} 부분 완료", - "일부 단계만 완료되어 후속 확인이나 재실행이 필요할 수 있습니다.", - "남은 단계나 누락된 결과를 확인하세요.", + "일부 단계만 완료되어 후속 확인이나 재시도가 필요할 수 있습니다.", + "후속 단계와 파일 결과를 확인해 주세요.", "#FFFBEA", "#A16207", "partial", @@ -114,7 +116,7 @@ internal static class ToolResultPresentationCatalog return "file_edit"; if (tool.Contains("file_write")) return "file_write"; - if (tool.Contains("file_read") || tool.Contains("glob") || tool.Contains("grep")) + if (tool.Contains("file_read") || tool.Contains("glob") || tool.Contains("grep") || tool.Contains("folder_map") || tool.Contains("multi_read")) return "filesystem"; if (tool.Contains("file")) return "file"; @@ -141,111 +143,93 @@ internal static class ToolResultPresentationCatalog return "generic"; } - private static string BuildSuccessLabel(string kind, string baseLabel) + private static string BuildSuccessLabel(string kind, string baseLabel) => kind switch { - return kind switch - { - "file_edit" => "파일 수정 완료", - "file_write" => "파일 쓰기 완료", - "filesystem" => "파일 탐색 완료", - "file" => "파일 작업 완료", - "build_test" => "빌드/테스트 완료", - "git" => "Git 작업 완료", - "document" => "문서 작업 완료", - "skill" => "스킬 실행 완료", - "mcp" => "MCP 도구 완료", - "question" => "의견 요청 완료", - "web" => "웹 요청 완료", - "command" => "명령 실행 완료", - _ => baseLabel, - }; - } + "file_edit" => "파일 수정 완료", + "file_write" => "파일 쓰기 완료", + "filesystem" => "파일 탐색 완료", + "file" => "파일 작업 완료", + "build_test" => "빌드/테스트 완료", + "git" => "Git 작업 완료", + "document" => "문서 작업 완료", + "skill" => "스킬 실행 완료", + "mcp" => "MCP 도구 완료", + "question" => "질문 요청 완료", + "web" => "웹 요청 완료", + "command" => "명령 실행 완료", + _ => baseLabel, + }; - private static string BuildFailureLabel(string kind, string baseLabel) + private static string BuildFailureLabel(string kind, string baseLabel) => kind switch { - return kind switch - { - "file_edit" => "파일 수정 실패", - "file_write" => "파일 쓰기 실패", - "filesystem" => "파일 탐색 실패", - "file" => "파일 작업 실패", - "build_test" => "빌드/테스트 실패", - "git" => "Git 작업 실패", - "document" => "문서 작업 실패", - "skill" => "스킬 실행 실패", - "mcp" => "MCP 도구 실패", - "question" => "의견 요청 실패", - "web" => "웹 요청 실패", - "command" => "명령 실행 실패", - _ => $"{baseLabel} 실패", - }; - } + "file_edit" => "파일 수정 실패", + "file_write" => "파일 쓰기 실패", + "filesystem" => "파일 탐색 실패", + "file" => "파일 작업 실패", + "build_test" => "빌드/테스트 실패", + "git" => "Git 작업 실패", + "document" => "문서 작업 실패", + "skill" => "스킬 실행 실패", + "mcp" => "MCP 도구 실패", + "question" => "질문 요청 실패", + "web" => "웹 요청 실패", + "command" => "명령 실행 실패", + _ => $"{baseLabel} 실패", + }; - private static string BuildSuccessDescription(string kind) + private static string BuildSuccessDescription(string kind) => kind switch { - return kind switch - { - "file_edit" => "파일 수정 결과가 저장되었습니다.", - "file_write" => "새 파일 작성 결과가 저장되었습니다.", - "filesystem" => "파일과 폴더 정보를 성공적으로 읽었습니다.", - "file" => "파일 관련 작업이 정상적으로 끝났습니다.", - "build_test" => "빌드 또는 테스트 단계가 성공적으로 끝났습니다.", - "git" => "Git 관련 작업이 정상적으로 끝났습니다.", - "document" => "문서 생성 또는 변환 작업이 완료되었습니다.", - "skill" => "선택한 스킬이 정상적으로 실행되었습니다.", - "mcp" => "등록된 MCP 도구 호출이 성공적으로 끝났습니다.", - "question" => "사용자 응답을 받아 다음 단계로 넘어갈 수 있습니다.", - "web" => "웹 요청이 정상적으로 끝났습니다.", - "command" => "명령 실행이 정상적으로 끝났습니다.", - _ => "요청한 작업이 정상적으로 완료되었습니다.", - }; - } + "file_edit" => "파일 수정 결과가 정상적으로 반영되었습니다.", + "file_write" => "새 파일 생성이나 쓰기 작업이 완료되었습니다.", + "filesystem" => "질문과 관련된 파일과 구조 정보를 찾았습니다.", + "file" => "파일 관련 작업이 정상적으로 끝났습니다.", + "build_test" => "빌드나 테스트 단계가 정상적으로 끝났습니다.", + "git" => "저장소 상태 확인 또는 변경 작업이 완료되었습니다.", + "document" => "문서 산출물 생성 또는 조립이 완료되었습니다.", + "skill" => "선택한 스킬이 정상적으로 실행되었습니다.", + "mcp" => "등록된 MCP 도구 호출이 완료되었습니다.", + "question" => "사용자 응답을 받아 다음 단계로 이어갈 수 있습니다.", + "web" => "필요한 웹 정보 조회가 완료되었습니다.", + "command" => "명령 실행이 완료되어 결과를 확인할 수 있습니다.", + _ => "요청한 작업이 정상적으로 완료되었습니다.", + }; - private static string BuildFailureDescription(string kind) + private static string BuildFailureDescription(string kind) => kind switch { - return kind switch - { - "file_edit" => "파일 변경 과정에서 문제가 발생했습니다.", - "file_write" => "파일 작성 또는 저장 과정에서 문제가 발생했습니다.", - "filesystem" => "파일/폴더 접근 중 문제가 발생했습니다.", - "file" => "파일 처리 중 문제가 발생했습니다.", - "build_test" => "빌드 또는 테스트 단계에서 실패가 발생했습니다.", - "git" => "Git 관련 작업이 실패했습니다.", - "document" => "문서 생성 또는 변환 작업이 실패했습니다.", - "skill" => "스킬 실행 중 문제가 발생했습니다.", - "mcp" => "MCP 도구 호출 중 문제가 발생했습니다.", - "question" => "사용자 의견 요청 과정에서 문제가 발생했습니다.", - "web" => "웹 요청 처리에 실패했습니다.", - "command" => "명령 실행 중 오류가 발생했습니다.", - _ => "작업 처리 중 오류가 발생했습니다.", - }; - } + "file_edit" => "파일 변경 과정에서 문제가 발생했습니다.", + "file_write" => "파일 생성 또는 쓰기 과정에서 문제가 발생했습니다.", + "filesystem" => "파일이나 폴더를 확인하는 과정에서 문제가 발생했습니다.", + "file" => "파일 처리 중 문제가 발생했습니다.", + "build_test" => "빌드 또는 테스트 단계에서 실패가 발생했습니다.", + "git" => "Git 작업 중 오류가 발생했습니다.", + "document" => "문서 생성 또는 조합 과정이 실패했습니다.", + "skill" => "스킬 실행 중 문제가 발생했습니다.", + "mcp" => "MCP 도구 호출 중 문제가 발생했습니다.", + "question" => "사용자 질문/응답 처리 단계에서 문제가 발생했습니다.", + "web" => "웹 요청 처리에 실패했습니다.", + "command" => "명령 실행 중 오류가 발생했습니다.", + _ => "작업 처리 중 오류가 발생했습니다.", + }; - private static string BuildSuccessFollowUp(string kind) + private static string BuildSuccessFollowUp(string kind) => kind switch { - return kind switch - { - "file_edit" or "file_write" => "변경 내용을 preview나 diff에서 다시 확인할 수 있습니다.", - "build_test" => "출력 로그와 후속 수정 필요 여부를 확인하세요.", - "git" => "브랜치 상태나 변경 요약을 이어서 확인하세요.", - "document" => "생성된 산출물 경로를 열어 결과를 확인하세요.", - "skill" => "같은 스킬을 다른 입력으로 이어서 실행할 수 있습니다.", - _ => "필요하면 후속 요청을 이어서 실행할 수 있습니다.", - }; - } + "file_edit" or "file_write" => "변경 내용은 preview나 diff에서 다시 확인할 수 있습니다.", + "build_test" => "출력 로그와 후속 수정 필요 여부를 확인해 주세요.", + "git" => "브랜치와 변경 수치를 이어서 확인해 주세요.", + "document" => "생성된 산출물 경로를 열어 결과를 확인해 주세요.", + "skill" => "같은 스킬을 다른 입력으로 이어서 실행할 수 있습니다.", + _ => "필요하면 다음 요청으로 이어서 작업할 수 있습니다.", + }; - private static string BuildFailureFollowUp(string kind) + private static string BuildFailureFollowUp(string kind) => kind switch { - return kind switch - { - "file_edit" or "file_write" => "대상 파일 경로와 권한, diff를 다시 확인하세요.", - "build_test" => "실패 로그와 컴파일 오류 메시지를 먼저 확인하세요.", - "git" => "현재 브랜치, 잠금 상태, 충돌 여부를 확인하세요.", - "document" => "입력 데이터와 출력 형식, 저장 위치를 다시 확인하세요.", - "skill" => "허용 도구와 런타임 요구사항을 다시 확인하세요.", - "web" => "연결 상태와 요청 대상 URL을 다시 확인하세요.", - "mcp" => "MCP 서버 연결 상태와 도구 등록 상태를 다시 확인하세요.", - _ => "같은 요청을 재시도하기 전에 원인 메시지를 먼저 확인하세요.", - }; - } + "file_edit" or "file_write" => "대상 파일 경로와 권한, diff 결과를 다시 확인해 주세요.", + "build_test" => "실패 로그와 컴파일/테스트 오류 메시지를 먼저 확인해 주세요.", + "git" => "브랜치 상태와 충돌 여부를 다시 확인해 주세요.", + "document" => "입력 데이터, 출력 형식, 대상 위치를 다시 확인해 주세요.", + "skill" => "사용 도구와 스킬 요구사항을 다시 확인해 주세요.", + "web" => "연결 상태와 요청 URL을 다시 확인해 주세요.", + "mcp" => "MCP 서버 연결 상태와 도구 등록 상태를 다시 확인해 주세요.", + _ => "같은 요청을 다시 시도하기 전에 원인 메시지를 먼저 확인해 주세요.", + }; } diff --git a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs index 2fa7aef..0c90533 100644 --- a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs +++ b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs @@ -11,6 +11,12 @@ namespace AxCopilot.Views; public partial class ChatWindow { + private string? _lastGroupedProcessFeedKey; + private int _lastGroupedProcessFeedIndex = -1; + private int _processFeedAppendCount; + private int _processFeedMergeCount; + private readonly Dictionary _transcriptRowKindCounts = new(); + private static Color ResolveLiveProgressAccentColor(Brush accentBrush) { return accentBrush is SolidColorBrush solid @@ -164,8 +170,24 @@ public partial class ChatWindow }; } - private void AddProcessFeedMessage(AgentEvent evt, string transcriptBadgeLabel, string itemDisplayName, string? eventSummaryText) + private void ResetProcessFeedGrouping() { + _lastGroupedProcessFeedKey = null; + _lastGroupedProcessFeedIndex = -1; + } + + private void TrackTranscriptRowKind(TranscriptRowKind kind) + { + if (_transcriptRowKindCounts.TryGetValue(kind, out var count)) + _transcriptRowKindCounts[kind] = count + 1; + else + _transcriptRowKindCounts[kind] = 1; + } + + private void AddProcessFeedMessage(AgentEvent evt, AgentTranscriptRowPresentation rowPresentation, string? eventSummaryText) + { + TrackTranscriptRowKind(rowPresentation.Kind); + var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray; var hintBg = TryFindResource("HintBackground") as Brush @@ -174,9 +196,9 @@ public partial class ChatWindow var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; var processMeta = BuildReadableProgressMetaText(evt); - var summary = BuildReadableProcessFeedSummary(evt, transcriptBadgeLabel, itemDisplayName).Trim(); + var summary = rowPresentation.Title.Trim(); if (string.IsNullOrWhiteSpace(summary)) - summary = transcriptBadgeLabel; + summary = rowPresentation.BadgeLabel; var msgMaxWidth = GetMessageMaxWidth(); var stack = new StackPanel @@ -203,7 +225,7 @@ public partial class ChatWindow ApplyLiveWaitingPulseToMarker(pulseMarker); stack.Children.Add(summaryRow); - var body = (eventSummaryText ?? string.Empty).Trim(); + var body = (string.IsNullOrWhiteSpace(eventSummaryText) ? rowPresentation.Description : eventSummaryText ?? string.Empty).Trim(); if (!string.IsNullOrWhiteSpace(body) && !string.Equals(body, summary, StringComparison.OrdinalIgnoreCase)) { @@ -241,7 +263,21 @@ public partial class ChatWindow stack.Children.Add(compactPathRow); } - AddTranscriptElement(stack); + if (rowPresentation.CanGroup && + !string.IsNullOrWhiteSpace(rowPresentation.GroupKey) && + string.Equals(_lastGroupedProcessFeedKey, rowPresentation.GroupKey, StringComparison.Ordinal) && + _lastGroupedProcessFeedIndex >= 0) + { + ReplaceTranscriptElement(_lastGroupedProcessFeedIndex, stack); + _processFeedMergeCount++; + } + else + { + AddTranscriptElement(stack); + _lastGroupedProcessFeedIndex = Math.Max(0, GetTranscriptElementCount() - 1); + _lastGroupedProcessFeedKey = rowPresentation.CanGroup ? rowPresentation.GroupKey : null; + _processFeedAppendCount++; + } } private static void ApplyLiveWaitingPulse(Border summaryRow) @@ -1151,6 +1187,9 @@ public partial class ChatWindow var toolResultPresentation = evt.Type == AgentEventType.ToolResult ? ToolResultPresentationCatalog.Resolve(evt, transcriptBadgeLabel) : null; + var rowPresentation = AgentTranscriptDisplayCatalog.ResolveRowPresentation(evt, itemDisplayName: evt.Type == AgentEventType.SkillCall + ? GetAgentItemDisplayName(evt.ToolName, slashPrefix: true) + : GetAgentItemDisplayName(evt.ToolName), transcriptBadgeLabel); var (icon, label, bgHex, fgHex) = isTotalStats ? ("\uE9D2", "전체 통계", "#F3EEFF", "#7C3AED") @@ -1179,11 +1218,39 @@ public partial class ChatWindow { eventSummaryText = evt.Type switch { - AgentEventType.PermissionRequest or AgentEventType.PermissionGranted => permissionPresentation?.Description ?? "", - AgentEventType.ToolResult => toolResultPresentation?.Description ?? "", - _ => "" + AgentEventType.PermissionRequest or AgentEventType.PermissionGranted => permissionPresentation?.Description ?? rowPresentation.Description, + AgentEventType.ToolResult => toolResultPresentation?.Description ?? rowPresentation.Description, + _ => rowPresentation.Description }; } + else if (string.IsNullOrWhiteSpace(rowPresentation.Description) == false && + string.Equals(eventSummaryText, evt.Summary, StringComparison.OrdinalIgnoreCase)) + { + eventSummaryText = rowPresentation.Description; + } + + if (IsProcessFeedEvent(evt)) + { + AddProcessFeedMessage(evt, rowPresentation, eventSummaryText); + return; + } + + ResetProcessFeedGrouping(); + TrackTranscriptRowKind(rowPresentation.Kind); + + if (rowPresentation.Kind == TranscriptRowKind.ToolResult && toolResultPresentation != null) + { + eventSummaryText = rowPresentation.Description; + } + else if (rowPresentation.Kind == TranscriptRowKind.Permission && permissionPresentation != null) + { + eventSummaryText = rowPresentation.Description; + } + + if (string.IsNullOrWhiteSpace(label) && !string.IsNullOrWhiteSpace(rowPresentation.BadgeLabel)) + label = rowPresentation.BadgeLabel; + if (string.IsNullOrWhiteSpace(eventSummaryText)) + eventSummaryText = rowPresentation.Description; // HTML/대용량 파일 내용이 이벤트 요약에 포함된 경우 인라인 표시 대신 1줄 요약으로 축소 if (!string.IsNullOrWhiteSpace(eventSummaryText) && evt.Type == AgentEventType.ToolResult @@ -1212,12 +1279,6 @@ public partial class ChatWindow if (evt.Type == AgentEventType.StepStart && evt.StepTotal > 0) UpdateProgressBar(evt); - if (IsProcessFeedEvent(evt)) - { - AddProcessFeedMessage(evt, transcriptBadgeLabel, itemDisplayName, eventSummaryText); - return; - } - var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray; var hintBg = TryFindResource("HintBackground") as Brush ?? BrushFromHex("#F8FAFC"); diff --git a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs index 47308ee..02dedcb 100644 --- a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -47,9 +47,9 @@ public partial class ChatWindow return preset.Description.Trim(); if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase)) - return "선택한 작업 유형에 맞춰 문서·데이터·파일 작업 흐름으로 이어집니다."; + return "선택된 작업 유형에 맞춰 문서, 데이터, 파일 작업 흐름으로 이어집니다."; - return "선택한 대화 주제에 맞춰 응답 방향과 초안 흐름을 정리합니다."; + return "선택된 대화 주제에 맞춰 응답 방향과 초안 흐름을 정리합니다."; } private void UpdateFolderBar() @@ -74,7 +74,7 @@ public partial class ChatWindow } else { - FolderPathLabel.Text = "폴더를 선택하세요"; + FolderPathLabel.Text = "폴더를 선택하세요."; FolderPathLabel.ToolTip = null; } @@ -129,7 +129,7 @@ public partial class ChatWindow memory.Load(workFolder); var docs = memory.InstructionDocuments; var learned = memory.All.Count; - var includePolicy = _settings.Settings.Llm.AllowExternalMemoryIncludes ? "외부 include 허용" : "외부 include 차단"; + var includePolicy = _settings.Settings.Llm.AllowExternalMemoryIncludes ? "?몃? include ?덉슜" : "?몃? include 李⑤떒"; var auditEnabled = _settings.Settings.Llm.EnableAuditLog; var recentIncludeEntries = AuditLogService.LoadRecent("MemoryInclude", maxCount: 5, daysBack: 3); @@ -143,7 +143,7 @@ public partial class ChatWindow var panel = new StackPanel { Margin = new Thickness(2) }; panel.Children.Add(new TextBlock { - Text = "메모리 상태", + Text = "硫붾え由??곹깭", FontSize = 13, FontWeight = FontWeights.SemiBold, Foreground = primaryText, @@ -151,7 +151,7 @@ public partial class ChatWindow }); panel.Children.Add(new TextBlock { - Text = $"계층형 규칙 {docs.Count}개 · 학습 메모리 {learned}개 · {includePolicy}", + Text = $"怨꾩링??洹쒖튃 {docs.Count}媛?쨌 ?숈뒿 硫붾え由?{learned}媛?쨌 {includePolicy}", FontSize = 11.5, Foreground = secondaryText, TextWrapping = TextWrapping.Wrap, @@ -163,7 +163,7 @@ public partial class ChatWindow panel.Children.Add(CreateSurfacePopupSeparator()); panel.Children.Add(new TextBlock { - Text = "적용 중 규칙", + Text = "?곸슜 以?洹쒖튃", FontSize = 12, FontWeight = FontWeights.SemiBold, Foreground = primaryText, @@ -177,7 +177,7 @@ public partial class ChatWindow { panel.Children.Add(new TextBlock { - Text = $"외 {docs.Count - 6}개 규칙", + Text = $"??{docs.Count - 6}媛?洹쒖튃", FontSize = 11, Foreground = secondaryText, Margin = new Thickness(8, 2, 8, 4), @@ -188,7 +188,7 @@ public partial class ChatWindow panel.Children.Add(CreateSurfacePopupSeparator()); panel.Children.Add(new TextBlock { - Text = "최근 include 감사", + Text = "理쒓렐 include 媛먯궗", FontSize = 12, FontWeight = FontWeights.SemiBold, Foreground = primaryText, @@ -199,7 +199,7 @@ public partial class ChatWindow { panel.Children.Add(new TextBlock { - Text = "감사 로그가 꺼져 있어 include 이력은 기록되지 않습니다.", + Text = "媛먯궗 濡쒓렇媛€ 爰쇱졇 ?덉뼱 include ?대젰?€ 湲곕줉?섏? ?딆뒿?덈떎.", FontSize = 11, Foreground = secondaryText, TextWrapping = TextWrapping.Wrap, @@ -210,7 +210,7 @@ public partial class ChatWindow { panel.Children.Add(new TextBlock { - Text = "최근 3일간 include 감사 기록이 없습니다.", + Text = "理쒓렐 3?쇨컙 include 媛먯궗 湲곕줉???놁뒿?덈떎.", FontSize = 11, Foreground = secondaryText, Margin = new Thickness(8, 0, 8, 6), @@ -245,7 +245,7 @@ public partial class ChatWindow return path; var directory = Path.GetDirectoryName(path); - return string.IsNullOrWhiteSpace(directory) ? fileName : $"{fileName} · {directory}"; + return string.IsNullOrWhiteSpace(directory) ? fileName : $"{fileName} 쨌 {directory}"; } catch { @@ -266,14 +266,14 @@ public partial class ChatWindow var stack = new StackPanel { Margin = new Thickness(8, 2, 8, 4) }; stack.Children.Add(new TextBlock { - Text = $"[{doc.Label}] 우선순위 {doc.Priority}", + Text = $"[{doc.Label}] ?곗꽑?쒖쐞 {doc.Priority}", FontSize = 11.5, FontWeight = FontWeights.SemiBold, Foreground = primaryText, }); stack.Children.Add(new TextBlock { - Text = meta.Count == 0 ? doc.Layer : $"{doc.Layer} · {string.Join(" · ", meta)}", + Text = meta.Count == 0 ? doc.Layer : $"{doc.Layer} 쨌 {string.Join(" 쨌 ", meta)}", FontSize = 10.5, Foreground = secondaryText, TextWrapping = TextWrapping.Wrap, @@ -297,13 +297,13 @@ public partial class ChatWindow private Border BuildMemoryPopupAuditRow(AuditEntry entry, Brush primaryText, Brush secondaryText, Brush okBrush, Brush warnBrush, Brush dangerBrush) { var statusBrush = entry.Success ? okBrush : dangerBrush; - var statusText = entry.Success ? "허용" : "차단"; + var statusText = entry.Success ? "?덉슜" : "李⑤떒"; var resultBrush = entry.Success ? secondaryText : warnBrush; var stack = new StackPanel { Margin = new Thickness(8, 2, 8, 4) }; stack.Children.Add(new TextBlock { - Text = $"{statusText} · {entry.Timestamp:HH:mm:ss}", + Text = $"{statusText} 쨌 {entry.Timestamp:HH:mm:ss}", FontSize = 11.5, FontWeight = FontWeights.SemiBold, Foreground = statusBrush, @@ -359,8 +359,9 @@ public partial class ChatWindow !string.IsNullOrWhiteSpace(m.Content) && (string.Equals(m.Role, "user", StringComparison.OrdinalIgnoreCase) || string.Equals(m.Role, "assistant", StringComparison.OrdinalIgnoreCase))) == true; + var hasVisibleExecution = conversation?.ExecutionEvents?.Count > 0; - if (string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase) || hasVisibleMessages || _isStreaming) + if (string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase) || hasVisibleMessages || hasVisibleExecution || _isStreaming) { SelectedPresetGuide.Visibility = Visibility.Collapsed; SelectedPresetGuideTitle.Text = ""; @@ -394,3 +395,4 @@ public partial class ChatWindow SelectedPresetGuide.Visibility = Visibility.Visible; } } + diff --git a/src/AxCopilot/Views/ChatWindow.TranscriptHost.cs b/src/AxCopilot/Views/ChatWindow.TranscriptHost.cs index 961ef4c..0a65e8c 100644 --- a/src/AxCopilot/Views/ChatWindow.TranscriptHost.cs +++ b/src/AxCopilot/Views/ChatWindow.TranscriptHost.cs @@ -117,6 +117,11 @@ public partial class ChatWindow { _transcriptElements.Clear(); _transcriptElementMap.Clear(); + _lastGroupedProcessFeedKey = null; + _lastGroupedProcessFeedIndex = -1; + _processFeedAppendCount = 0; + _processFeedMergeCount = 0; + _transcriptRowKindCounts.Clear(); } private void RemoveTranscriptElement(UIElement element) diff --git a/src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs b/src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs index 37c88a6..bec3e87 100644 --- a/src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs +++ b/src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Linq; using System.Windows.Threading; using AxCopilot.Models; using AxCopilot.Services; @@ -95,6 +96,11 @@ public partial class ChatWindow renderedItems = renderPlan.NewKeys.Count, hiddenCount = renderPlan.HiddenCount, transcriptElements = GetTranscriptElementCount(), + processFeedAppends = _processFeedAppendCount, + processFeedMerges = _processFeedMergeCount, + rowKindCounts = _transcriptRowKindCounts.ToDictionary( + pair => pair.Key.ToString(), + pair => pair.Value), }); }