diff --git a/README.md b/README.md index b53d835..0b47501 100644 --- a/README.md +++ b/README.md @@ -833,6 +833,9 @@ ow + toggle 시각 언어로 통일했습니다. - 큰 카드형 요소도 더 `claw-code` 쪽으로 눌렀습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AddPlanningCard(...)` 에서 계획 카드 라운드, 패딩, 헤더 아이콘/텍스트, 진행률 텍스트, 단계 행 폰트를 전반적으로 줄였고, 계획 헤더 문구도 더 짧게 정리했습니다. - 같은 변경에서 `CreateCompactEventPill(...)`, `CreateTimelineLoadMoreCard(...)`도 함께 축소해 컨텍스트 압축 pill과 “이전 대화 더 보기” 카드가 본문보다 과하게 두껍게 보이지 않도록 맞췄습니다. - 업데이트: 2026-04-05 18:01 (KST) +- 엔진 마감도 한 단계 더 진행했습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `ExecutePreparedTurnAsync(...)`를 추가해 `send`와 `regenerate`가 같은 실행/예외/취소/최종 커밋/후처리 경로를 타도록 묶었습니다. 이제 전송과 재생성은 같은 prepared-execution 축에서 닫히고, 실패 토스트와 최종 assistant 커밋도 같은 helper가 담당합니다. +- 같은 변경에서 계획 이벤트는 기본적으로 큰 카드가 아니라 얇은 요약 pill로만 보이고, `debug` 로그일 때만 `AddPlanningCard(...)`가 펼쳐지도록 바꿨습니다. 문서형 Cowork/Code 작업에서도 기본 노출이 더 `claw-code`처럼 차분한 상태가 됐습니다. +- 업데이트: 2026-04-05 18:08 (KST) - 좌측 패널과 하단 바도 `claw-code` 쪽 밀도로 다시 맞췄습니다. [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 사이드바 폭을 줄이고, 헤더 앱 배지를 강조색 채운 정사각형 대신 `HintBackground + BorderColor` 기반의 작은 배지형으로 바꿨습니다. - `새 대화`, `검색`, `작업 유형/워크스페이스`, 하단 사용자 영역, 삭제 영역까지 패딩과 폰트, 아이콘 크기를 함께 낮췄고, 하단 상태바는 다이아몬드 아이콘을 작은 원형 점으로 바꿔 더 단순한 상태선처럼 보이게 정리했습니다. - 실행 로그 배너도 본문 침범을 더 줄였습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AddAgentEventBanner(...)` 에서 debug 전용 `ToolInput` 카드 길이를 더 짧게 줄였고, `FilePath`는 일반 로그에서는 빠른 액션이 붙은 카드형 대신 파일명 한 줄만 약하게 표시하도록 바꿨습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index add102b..687c9f9 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -4612,3 +4612,7 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - 같은 변경에서 `CreateCompactEventPill(...)`, `CreateTimelineLoadMoreCard(...)`도 함께 축소해 컨텍스트 압축 pill과 “이전 대화 더 보기” 카드가 본문보다 과하게 두껍게 보이지 않도록 맞췄습니다. - 검증 예정: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` - 업데이트: 2026-04-05 18:01 (KST) +- 엔진 마감도 한 단계 더 진행했습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `ExecutePreparedTurnAsync(...)`를 추가해 `send`와 `regenerate`가 같은 실행/예외/취소/최종 커밋/후처리 helper를 타도록 묶었습니다. 이로써 prepared-execution 이후 마감 경로도 하나로 수렴했습니다. +- 같은 변경에서 계획 이벤트는 기본적으로 큰 카드가 아니라 얇은 요약 pill로만 보이고, `debug` 로그일 때만 `AddPlanningCard(...)`가 펼쳐지도록 바꿨습니다. 문서형 Cowork/Code 작업에서도 기본 노출이 더 최소화됐습니다. +- 검증 예정: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` +- 업데이트: 2026-04-05 18:08 (KST) diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index 6a35eee..952441b 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -8372,30 +8372,17 @@ public partial class ChatWindow : Window } } - var response = await _chatEngine.ExecutePreparedAsync( + var executionOutcome = await ExecutePreparedTurnAsync( + conv, + runTab, + originTab, preparedExecution, - (messages, token) => RunAgentLoopAsync(runTab, originTab, conv, messages, token), - (messages, token) => _llm.SendAsync(messages.ToList(), token), - _streamCts.Token); - assistantContent = response; - StopAiIconPulse(); - _cachedStreamContent = response; - - draftSucceeded = true; - } - catch (OperationCanceledException) - { - var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, cancelled: true); - assistantContent = finalized.Content; - draftCancelled = finalized.Cancelled; - draftFailure = finalized.FailureReason; - } - catch (Exception ex) - { - var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, ex); - assistantContent = finalized.Content; - ShowToast("실패한 요청은 작업 요약에서 다시 시도할 수 있습니다.", "\uE783", 2600); - draftFailure = finalized.FailureReason; + "응답 생성 중..."); + conv = executionOutcome.Conversation; + assistantContent = executionOutcome.AssistantContent; + draftSucceeded = executionOutcome.DraftSucceeded; + draftCancelled = executionOutcome.DraftCancelled; + draftFailure = executionOutcome.DraftFailure; } finally { @@ -8405,18 +8392,7 @@ public partial class ChatWindow : Window _llm.ClearRouteOverride(); UpdateModelLabel(); } - ResetStreamingUiState(); } - - lock (_convLock) - { - var session = ChatSession; - assistantContent = _chatEngine.FinalizeAssistantTurn(session, conv, runTab, assistantContent, _storage); - _currentConversation = session?.CurrentConversation ?? conv; - conv = _currentConversation!; - } - - FinalizeConversationTurn(originTab, conv); FinalizeQueuedDraft(originTab, queuedDraftId, draftSucceeded, draftCancelled, draftFailure); } @@ -8553,6 +8529,87 @@ public partial class ChatWindow : Window _ = Dispatcher.BeginInvoke(new Action(() => StartNextQueuedDraftIfAny()), DispatcherPriority.Background); } + private sealed record PreparedTurnOutcome( + ChatConversation Conversation, + string AssistantContent, + bool DraftSucceeded, + bool DraftCancelled, + string? DraftFailure); + + private async Task ExecutePreparedTurnAsync( + ChatConversation conversation, + string runTab, + string rememberTab, + AxAgentExecutionEngine.PreparedExecution preparedExecution, + string busyStatus) + { + _isStreaming = true; + _streamRunTab = runTab; + BtnSend.IsEnabled = false; + BtnSend.Visibility = Visibility.Collapsed; + BtnStop.Visibility = Visibility.Visible; + if (runTab == "Cowork" || runTab == "Code") + BtnPause.Visibility = Visibility.Visible; + _streamCts = new CancellationTokenSource(); + ForceScrollToEnd(); + + var assistantContent = string.Empty; + var draftSucceeded = false; + var draftCancelled = false; + string? draftFailure = null; + + _activeStreamText = null; + _cachedStreamContent = ""; + _displayedLength = 0; + _cursorVisible = true; + _aiIconPulseStopped = false; + _streamStartTime = DateTime.UtcNow; + _elapsedTimer.Start(); + SetStatus(busyStatus, spinning: true); + + try + { + var response = await _chatEngine.ExecutePreparedAsync( + preparedExecution, + (messages, token) => RunAgentLoopAsync(runTab, rememberTab, conversation, messages, token), + (messages, token) => _llm.SendAsync(messages.ToList(), token), + _streamCts.Token); + assistantContent = response; + StopAiIconPulse(); + _cachedStreamContent = response; + draftSucceeded = true; + } + catch (OperationCanceledException) + { + var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, cancelled: true); + assistantContent = finalized.Content; + draftCancelled = finalized.Cancelled; + draftFailure = finalized.FailureReason; + } + catch (Exception ex) + { + var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, ex); + assistantContent = finalized.Content; + draftFailure = finalized.FailureReason; + ShowToast("실패한 요청은 작업 요약에서 다시 시도할 수 있습니다.", "\uE783", 2600); + } + finally + { + ResetStreamingUiState(); + } + + lock (_convLock) + { + var session = ChatSession; + assistantContent = _chatEngine.FinalizeAssistantTurn(session, conversation, runTab, assistantContent, _storage); + _currentConversation = session?.CurrentConversation ?? conversation; + conversation = _currentConversation!; + } + + FinalizeConversationTurn(rememberTab, conversation); + return new PreparedTurnOutcome(conversation, assistantContent, draftSucceeded, draftCancelled, draftFailure); + } + private void ScheduleExecutionHistoryRender(bool autoScroll = true) { _pendingExecutionHistoryAutoScroll |= autoScroll; @@ -10287,10 +10344,29 @@ public partial class ChatWindow : Window { var logLevel = _settings.Settings.Llm.AgentLogLevel; - // Planning 이벤트는 단계 목록 카드로 별도 렌더링 + // Planning 이벤트는 기본적으로 얇은 요약만 보이고, debug에서만 큰 카드로 펼칩니다. if (evt.Type == AgentEventType.Planning && evt.Steps is { Count: > 0 }) { - AddPlanningCard(evt); + if (string.Equals(logLevel, "debug", StringComparison.OrdinalIgnoreCase)) + { + AddPlanningCard(evt); + } + else + { + var compactPrimaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; + var compactSecondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; + var compactHintBg = TryFindResource("HintBackground") as Brush + ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); + var compactBorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; + var compactAccentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; + var summary = !string.IsNullOrWhiteSpace(evt.Summary) + ? evt.Summary! + : $"계획 {evt.Steps.Count}단계"; + var pill = CreateCompactEventPill(summary, compactPrimaryText, compactSecondaryText, compactHintBg, compactBorderBrush, compactAccentBrush); + pill.Opacity = 0; + pill.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160))); + MessagePanel.Children.Add(pill); + } return; } @@ -10876,64 +10952,15 @@ public partial class ChatWindow : Window private async Task SendRegenerateAsync(ChatConversation conv) { var runTab = NormalizeTabName(conv.Tab); - _isStreaming = true; - _streamRunTab = runTab; - BtnSend.IsEnabled = false; - BtnSend.Visibility = Visibility.Collapsed; - BtnStop.Visibility = Visibility.Visible; - _streamCts = new CancellationTokenSource(); - ForceScrollToEnd(); // 응답 시작 시 강제 하단 이동 - - var assistantContent = string.Empty; - _activeStreamText = null; - _cachedStreamContent = ""; - _displayedLength = 0; - _cursorVisible = true; - _aiIconPulseStopped = false; - _streamStartTime = DateTime.UtcNow; - _elapsedTimer.Start(); - SetStatus("에이전트 작업 중...", spinning: true); - - try - { - List sendMessages; - AxAgentExecutionEngine.PreparedExecution preparedExecution; - lock (_convLock) - preparedExecution = PrepareExecutionForConversation(conv, runTab, null); - var executionMode = preparedExecution.Mode; - sendMessages = preparedExecution.Messages; - - var response = await _chatEngine.ExecutePreparedAsync( - preparedExecution, - (messages, token) => RunAgentLoopAsync(runTab, runTab, conv, messages, token), - (messages, token) => _llm.SendAsync(messages.ToList(), token), - _streamCts.Token); - assistantContent = response; - StopAiIconPulse(); - _cachedStreamContent = response; - } - catch (OperationCanceledException) - { - assistantContent = _chatEngine.FinalizeExecutionContent(assistantContent, cancelled: true).Content; - } - catch (Exception ex) - { - assistantContent = _chatEngine.FinalizeExecutionContent(assistantContent, ex).Content; - ShowToast("실패한 요청은 작업 요약에서 다시 시도할 수 있습니다.", "\uE783", 2600); - } - finally - { - ResetStreamingUiState(); - } - + AxAgentExecutionEngine.PreparedExecution preparedExecution; lock (_convLock) - { - var session = ChatSession; - assistantContent = _chatEngine.FinalizeAssistantTurn(session, conv, runTab, assistantContent, _storage); - _currentConversation = session?.CurrentConversation ?? conv; - conv = _currentConversation!; - } - FinalizeConversationTurn(conv.Tab ?? _activeTab, conv); + preparedExecution = PrepareExecutionForConversation(conv, runTab, null); + await ExecutePreparedTurnAsync( + conv, + runTab, + conv.Tab ?? _activeTab, + preparedExecution, + "에이전트 작업 중..."); } /// 채팅 본문 폭을 세 탭에서 동일한 기준으로 맞춥니다.