diff --git a/README.md b/README.md index 619d50c..c369b12 100644 --- a/README.md +++ b/README.md @@ -747,6 +747,8 @@ ow + toggle 시각 언어로 통일했습니다. - 추가로 실행 이벤트/실행 기록 저장도 지연 저장으로 바꿨습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AppendConversationExecutionEvent()`와 `AppendConversationAgentRun()`은 이제 이벤트마다 바로 `_storage.Save(...)`를 호출하지 않고, `ScheduleConversationPersist()`를 통해 220ms 단위로 묶어서 flush 합니다. Cowork/Code의 연속 이벤트 구간에서 저장 I/O가 덜 붙도록 만든 조정입니다. - 이번엔 실행 완료 뒤 메시지 축을 흔들던 보조 UI를 더 줄였습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `RenderSuggestActionChips()`는 더 이상 본문 `MessagePanel`에 제안 칩을 직접 삽입하지 않고, 요약 토스트만 띄우도록 바꿨습니다. 이 변경으로 Cowork/Code 작업 중간에 제안 칩이 본문 폭과 스크롤 위치를 흔들던 경로를 끊었습니다. - 같은 파일의 대기열 UI도 기본 축약형으로 바꿨습니다. `DraftQueuePanel`은 이제 기본적으로 요약 pill + 핵심 항목 1개만 보이고, 필요할 때만 `상세 보기`로 전체 섹션 카드(`실행 중/다음 작업/보류/완료/실패`)를 펼칩니다. 대기열 카드가 매번 크게 다시 그려지면서 컴포저 위 레이아웃을 밀던 현상을 줄이기 위한 정리입니다. +- 이어서 Cowork/Code 완료 직후 저장 축도 정리했습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `ResetStreamingUiState()`는 이제 배치 저장 대기 중인 실행 이벤트/실행 기록을 먼저 `FlushPendingConversationPersists()`로 확정 저장한 뒤 타이머를 내립니다. 이걸로 실행 종료 직전 들어온 마지막 이벤트가 지연 저장 타이머만 멈춘 채 사라질 수 있는 경로를 막았습니다. +- 같은 수정에서 `PersistConversationSnapshot(...)`를 추가해 중간 저장, 최종 저장, 지연 저장 flush를 한 경로로 묶었고, `RunAgentLoopAsync(...)` 안의 중복 `_storage.Save(...)` / `RememberConversation(...)`는 제거했습니다. 이제 Cowork/Code 완료 시점 저장은 `FinalizeConversationTurn(...)` 쪽의 단일 완료 경로가 맡습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 - 업데이트: 2026-04-05 12:24 (KST) - 업데이트: 2026-04-05 12:31 (KST) @@ -757,6 +759,7 @@ ow + toggle 시각 언어로 통일했습니다. - 업데이트: 2026-04-05 12:58 (KST) - 업데이트: 2026-04-05 13:03 (KST) - 업데이트: 2026-04-05 13:12 (KST) +- 업데이트: 2026-04-05 13:20 (KST) --- diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 0bb5c62..ffe56d2 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -4515,3 +4515,7 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - 실행 완료 뒤 메시지 컬럼을 크게 흔들던 보조 UI를 더 줄였습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `RenderSuggestActionChips()`는 더 이상 본문 `MessagePanel`에 직접 제안 칩 컨테이너를 추가하지 않고, 제안 라벨 요약만 토스트로 표시합니다. Cowork/Code에서 `suggest_actions` 결과가 들어올 때 본문 레이아웃과 스크롤이 흔들리던 경로를 먼저 끊는 안정화 조치입니다. - 같은 파일의 `DraftQueuePanel`도 기본 축약 표시로 바꿨습니다. 대기열은 처음부터 모든 섹션 카드를 다 렌더하지 않고, `CreateDraftQueueSummaryStrip(...)`의 요약 pill과 `CreateCompactDraftQueuePanel(...)`의 핵심 항목 한 장만 먼저 보여 줍니다. 사용자가 `상세 보기`를 누를 때만 `실행 중 / 다음 작업 / 보류 / 완료 / 실패` 섹션이 펼쳐집니다. - 이번 조정으로 AX Agent 채팅 엔진 공통화 작업 중에도 보조 카드가 컴포저 위 레이아웃을 크게 밀어 올리거나, 실행 중간에 메시지 축을 흔드는 체감을 줄이는 방향으로 정리했습니다. 다음 단계는 이 축약형 UI 위에서 Cowork/Code 완료 카드와 큐 완료 후처리를 더 엔진 중심으로 맞추는 것입니다. +- 업데이트: 2026-04-05 13:20 (KST) +- Cowork/Code 완료 직후 저장 경로도 한 단계 더 정리했습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `PersistConversationSnapshot(...)`을 추가해 중간 저장, 최종 저장, 지연 저장 flush를 같은 helper가 담당하도록 만들었습니다. +- `ResetStreamingUiState()`는 이제 스트리밍 UI 타이머를 내리기 전에 `FlushPendingConversationPersists()`를 먼저 호출합니다. 이 변경은 실행 종료 직전 들어온 마지막 `ExecutionEvent` 또는 `AgentRun`이 지연 저장 큐에만 남아 있다가 타이머 중지와 함께 누락될 수 있는 경로를 막기 위한 것입니다. +- 같은 변경에서 `RunAgentLoopAsync(...)` 내부의 직접 `_storage.Save(...)` / `RememberConversation(...)`는 제거했습니다. Cowork/Code 완료 시점 저장은 이제 `FinalizeConversationTurn(...)`의 단일 완료 경로에서만 수행되어, AgentLoop 내부 저장과 완료 후 저장이 중복으로 겹치던 경로를 줄였습니다. diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index 9bab645..05d95db 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -8321,15 +8321,7 @@ public partial class ChatWindow : Window return; lastAutoSaveUtc = DateTime.UtcNow; - try - { - _storage.Save(conv); - ChatSession?.RememberConversation(originTab, conv.Id); - } - catch (Exception ex) - { - Services.LogService.Debug($"대화 중간 저장 실패: {ex.Message}"); - } + PersistConversationSnapshot(originTab, conv, "대화 중간 저장 실패"); } TryPersistConversation(force: true); @@ -8500,16 +8492,6 @@ public partial class ChatWindow : Window { _agentLoop.ActiveTab = runTab; var response = await _agentLoop.RunAsync(sendMessages.ToList(), cancellationToken); - try - { - _storage.Save(conversation); - ChatSession?.RememberConversation(originTab, conversation.Id); - } - catch (Exception ex) - { - Services.LogService.Debug($"에이전트 루프 저장 실패: {ex.Message}"); - } - if (_settings.Settings.Llm.NotifyOnComplete) { var title = string.Equals(runTab, "Code", StringComparison.OrdinalIgnoreCase) @@ -8560,6 +8542,7 @@ public partial class ChatWindow : Window private void ResetStreamingUiState() { + FlushPendingConversationPersists(); _cursorTimer.Stop(); _elapsedTimer.Stop(); _typingTimer.Stop(); @@ -8590,8 +8573,7 @@ public partial class ChatWindow : Window RenderMessages(preserveViewport: true); AutoScrollIfNeeded(); - try { _storage.Save(conversation); } catch (Exception ex) { Services.LogService.Debug($"대화 저장 실패: {ex.Message}"); } - ChatSession?.RememberConversation(rememberTab, conversation.Id); + PersistConversationSnapshot(rememberTab, conversation, "대화 저장 실패"); SyncTabConversationIdsFromSession(); RefreshConversationList(); } @@ -8646,6 +8628,23 @@ public partial class ChatWindow : Window _conversationPersistTimer.Start(); } + private void PersistConversationSnapshot(string rememberTab, ChatConversation conversation, string failureLabel) + { + if (conversation == null || string.IsNullOrWhiteSpace(conversation.Id)) + return; + + _pendingConversationPersists.Remove(conversation.Id); + try + { + _storage.Save(conversation); + ChatSession?.RememberConversation(rememberTab, conversation.Id); + } + catch (Exception ex) + { + Services.LogService.Debug($"{failureLabel}: {ex.Message}"); + } + } + private void FlushPendingConversationPersists() { if (_pendingConversationPersists.Count == 0) @@ -8653,8 +8652,7 @@ public partial class ChatWindow : Window foreach (var conversation in _pendingConversationPersists.Values.ToList()) { - try { _storage.Save(conversation); } - catch (Exception ex) { Services.LogService.Debug($"대화 지연 저장 실패: {ex.Message}"); } + PersistConversationSnapshot(conversation.Tab ?? _activeTab, conversation, "대화 지연 저장 실패"); } _pendingConversationPersists.Clear();