From e2553a10aeb6de1eee384cb321cba7e2a0067f9b Mon Sep 17 00:00:00 2001 From: lacvet Date: Wed, 15 Apr 2026 22:37:13 +0900 Subject: [PATCH] AX Agent background conversation ??? UI ?? ?? ?? ??? ?? ??? ?? ?? ?? ??? ??? ?? ?? ?? ???? ??? ????. ChatStreamingUiPolicy? ?? ??? ??? ActiveConversation ???? ???, ChatWindow? ??? ??? ?? ??? ????. AgentProgressHintTimer? StreamMetricsLabel? background conversation ????? ??? UI? ?? ??? ??? ????, ?? ???? README/DEVELOPMENT ??? ????. ??: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_background_conversation_live_ui\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui\\ ??: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|AppStateServiceTests" -p:OutputPath=bin\\verify_background_conversation_live_ui_tests\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui_tests\\ --- README.md | 7 +++++++ docs/DEVELOPMENT.md | 9 +++++++++ .../Views/ChatStreamingUiPolicyTests.cs | 4 ++-- src/AxCopilot/Views/ChatStreamingUiPolicy.cs | 2 +- .../Views/ChatWindow.AgentStatusPresentation.cs | 13 +++++++++++++ .../Views/ChatWindow.StatusPresentation.cs | 8 +++++++- src/AxCopilot/Views/ChatWindow.xaml.cs | 16 +++++++++++++--- 7 files changed, 52 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d7e7937..60ee9e8 100644 --- a/README.md +++ b/README.md @@ -2302,3 +2302,10 @@ MIT License - 테스트: - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_tab_loop_isolation\\ -p:IntermediateOutputPath=obj\\verify_tab_loop_isolation\\` 경고 0 / 오류 0 - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AppStateServiceTests" -p:OutputPath=bin\\verify_tab_loop_isolation_tests\\ -p:IntermediateOutputPath=obj\\verify_tab_loop_isolation_tests\\` 통과 45 +?낅뜲?댄듃: 2026-04-15 22:39 (KST) +- AX Agent 媛숈? ???덉쓽 ?ㅻⅨ ?€?붾? 蹂닿퀬 ?덉쓣 ??, ?щ쒕? ?꾨씪?대툕 移대뱶?€ ?ㅽ뻾 以??ㅽ???ㅻ벐 ?ㅼ븘?섎굹??븯?쇰뒗 ?뚭?瑜??섏젙?덉뒿?덈떎. `src/AxCopilot/Views/ChatStreamingUiPolicy.cs`?먯꽌 ?곷떒 ?쇱씠釉?媛€?대뱶 ?몄텧??`ActiveConversation`?먯꽌留?媛€?ν븯寃?留뚮뱾???낅땲?? +- `src/AxCopilot/Views/ChatWindow.xaml.cs`??`GetGuideVisibilityForActiveTab()`?쇰줈 ?꾩옱 ?좏깮?섏씠吏€???ㅽ듃由щ컢 媛€?ν꽦???듬났 愿€由ы븯怨? `RefreshStreamingControlsForActiveTab()`?먯꽌 ?쇱씠釉?移대뱶, ?곹깭 諛?, ?낅젰李?諛붾줈 ???쒓컙쨌?좏겙 ?섏씠釉붿쓣 媛숈? 湲곗쨪?쇰줈 泥섎━?섎룄濡?留욎톬?듬땲?? +- `src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs`?€ `src/AxCopilot/Views/ChatWindow.StatusPresentation.cs`?먯꽌 idle ?€?몄뿉 background conversation?댁뼱?ㅽ듃由щ컢 ?뚰듃?€ 硫뷀듃由?以꾩쓣 ?ㅼ떆 ?뚮━吏€ ?딆쾶 ??諛꾧퉴吏€ 媛숈? ?대? ?쇱씠釉?UI媛€ 媛꾩슂?먯뿉 寃⑥?蹂댁씠吏€ ?딄쾶 ?섏젙?덉뒿?덈떎. +- 寃€利? + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_background_conversation_live_ui\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui\\` 寃쎄퀬 0 / ?ㅻ쪟 0 + - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|AppStateServiceTests" -p:OutputPath=bin\\verify_background_conversation_live_ui_tests\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui_tests\\` ?듦낵 49 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 07443e0..6e35584 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1625,3 +1625,12 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫 - `src/AxCopilot/Services/AppStateService.cs`는 `GetAgentRunForTab(...)`과 탭 지정 `ApplyAgentEvent(...)`를 지원하도록 확장했고, `src/AxCopilot/Views/ChatWindow.ConversationListPresentation.cs`, `src/AxCopilot/Views/ChatWindow.TaskSummary.cs`가 현재 활성 탭의 run 메타를 읽도록 변경했습니다. - 테스트: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_tab_loop_isolation\\ -p:IntermediateOutputPath=obj\\verify_tab_loop_isolation\\` 경고 0 / 오류 0 - 테스트: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AppStateServiceTests" -p:OutputPath=bin\\verify_tab_loop_isolation_tests\\ -p:IntermediateOutputPath=obj\\verify_tab_loop_isolation_tests\\` 통과 45 +업데이트: 2026-04-15 22:39 (KST) +- AX Agent 동일 탭 내 background conversation 표시 정책을 다시 정리했습니다. 기존 `src/AxCopilot/Views/ChatStreamingUiPolicy.cs`는 `BackgroundConversation`에도 상단 라이브 가이드를 유지하도록 되어 있어, 사용자가 같은 탭의 다른 대화를 보고 있을 때 idle 타이머가 다시 실행중 카드와 상태 바를 살려내는 회귀가 있었습니다. +- `ChatStreamingUiPolicy.ShouldShowTopLevelGuide(...)`를 `ActiveConversation` 전용으로 좁히고, `src/AxCopilot/Views/ChatWindow.xaml.cs`에는 `GetGuideVisibilityForActiveTab()`를 추가해 `RefreshStreamingControlsForActiveTab()`와 실시간 이벤트 렌더 분기가 동일한 가시성 기준을 공유하도록 맞췄습니다. +- `src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs`의 `AgentProgressHintTimer_Tick(...)`는 background conversation 상태에서 `RemoveAgentLiveCard(animated: false)`, `HideStreamingStatusBar()`, `HideStickyProgress()`를 유지하고 실행 힌트 상태만 탭별로 보존합니다. 이로써 사용자가 다른 대화에 머무는 동안에는 실행중 카드가 다시 생기지 않고, 실제 실행 대화로 돌아왔을 때만 최신 run 상태가 다시 복원됩니다. +- `src/AxCopilot/Views/ChatWindow.StatusPresentation.cs`의 `UpdateStreamMetricsLabel(...)`도 같은 기준을 따르도록 조정했습니다. `_isStreaming`만 보던 기존 로직과 달리, 현재 활성 대화가 실제 스트리밍 대화일 때만 시간/토큰 라벨을 표시해 background conversation에서 메트릭 줄이 다시 나타나는 회귀를 막습니다. +- 테스트: `src/AxCopilot.Tests/Views/ChatStreamingUiPolicyTests.cs`의 상단 가이드 가시성 기대값을 갱신했습니다. +- 검증: + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_background_conversation_live_ui\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui\\` 경고 0 / 오류 0 + - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|AppStateServiceTests" -p:OutputPath=bin\\verify_background_conversation_live_ui_tests\\ -p:IntermediateOutputPath=obj\\verify_background_conversation_live_ui_tests\\` 통과 49 diff --git a/src/AxCopilot.Tests/Views/ChatStreamingUiPolicyTests.cs b/src/AxCopilot.Tests/Views/ChatStreamingUiPolicyTests.cs index 69470a4..aba05bf 100644 --- a/src/AxCopilot.Tests/Views/ChatStreamingUiPolicyTests.cs +++ b/src/AxCopilot.Tests/Views/ChatStreamingUiPolicyTests.cs @@ -27,8 +27,8 @@ public class ChatStreamingUiPolicyTests [Theory] [InlineData(nameof(StreamingGuideVisibility.Hidden), false)] [InlineData(nameof(StreamingGuideVisibility.ActiveConversation), true)] - [InlineData(nameof(StreamingGuideVisibility.BackgroundConversation), true)] - public void ShouldShowTopLevelGuide_ShouldKeepGuideVisibleForBackgroundConversation( + [InlineData(nameof(StreamingGuideVisibility.BackgroundConversation), false)] + public void ShouldShowTopLevelGuide_ShouldOnlyShowGuideForVisibleStreamingConversation( string visibilityName, bool expected) { diff --git a/src/AxCopilot/Views/ChatStreamingUiPolicy.cs b/src/AxCopilot/Views/ChatStreamingUiPolicy.cs index d40d883..db2c70c 100644 --- a/src/AxCopilot/Views/ChatStreamingUiPolicy.cs +++ b/src/AxCopilot/Views/ChatStreamingUiPolicy.cs @@ -24,7 +24,7 @@ internal static class ChatStreamingUiPolicy } internal static bool ShouldShowTopLevelGuide(StreamingGuideVisibility visibility) - => visibility != StreamingGuideVisibility.Hidden; + => visibility == StreamingGuideVisibility.ActiveConversation; internal static bool ShouldRenderConversationBoundContent(StreamingGuideVisibility visibility) => visibility == StreamingGuideVisibility.ActiveConversation; diff --git a/src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs b/src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs index 4f2ba41..06a2320 100644 --- a/src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.AgentStatusPresentation.cs @@ -319,6 +319,19 @@ public partial class ChatWindow return; } + var guideVisibility = GetGuideVisibilityForActiveTab(); + if (!ChatStreamingUiPolicy.ShouldShowTopLevelGuide(guideVisibility)) + { + RemoveAgentLiveCard(animated: false); + HideStreamingStatusBar(); + HideStickyProgress(); + UpdateLiveAgentProgressHint(null, runTab: _activeTab); + UpdateStreamMetricsLabel( + (int)Math.Max(0, _tabCumulativeInputTokens.GetValueOrDefault(_activeTab)), + (int)Math.Max(0, _tabCumulativeOutputTokens.GetValueOrDefault(_activeTab))); + return; + } + // _streamingTabs.Contains(_activeTab)가 위에서 이미 검증됨 — _streamRunTab은 다른 탭 시작 시 덮어써질 수 있으므로 _activeTab 사용 var runTab = _activeTab; if (!string.Equals(runTab, "Cowork", StringComparison.OrdinalIgnoreCase) diff --git a/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs b/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs index cc403d5..082c18e 100644 --- a/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs @@ -236,12 +236,18 @@ public partial class ChatWindow { if (StreamMetricsLabel == null) return; - if (!_isStreaming) + if (!_isStreaming || !ChatStreamingUiPolicy.ShouldShowTopLevelGuide(GetGuideVisibilityForActiveTab())) { StreamMetricsLabel.Visibility = Visibility.Collapsed; return; } + if (inputTokens == 0 && outputTokens == 0) + { + inputTokens = (int)Math.Max(0, _tabCumulativeInputTokens.GetValueOrDefault(_activeTab)); + outputTokens = (int)Math.Max(0, _tabCumulativeOutputTokens.GetValueOrDefault(_activeTab)); + } + StreamMetricsLabel.Visibility = Visibility.Visible; var elapsedText = "0s"; diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index cf851a6..4d7bfc6 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -1684,9 +1684,7 @@ public partial class ChatWindow : Window private void RefreshStreamingControlsForActiveTab() { var isOwningTab = _streamingTabs.Contains(_activeTab); - var guideVisibility = ChatStreamingUiPolicy.ResolveGuideVisibility( - isOwningTab, - isOwningTab && IsStreamingConversationVisible(_activeTab)); + var guideVisibility = GetGuideVisibilityForActiveTab(); var shouldShowTopLevelGuide = ChatStreamingUiPolicy.ShouldShowTopLevelGuide(guideVisibility); // 실행 중에도 Send 버튼 표시 — 메시지가 큐에 추가됨 @@ -1724,6 +1722,10 @@ public partial class ChatWindow : Window if (pendingUiEvent != null) UpdateAgentLiveCardV2(pendingUiEvent); } + + UpdateStreamMetricsLabel( + (int)Math.Max(0, _tabCumulativeInputTokens.GetValueOrDefault(_activeTab)), + (int)Math.Max(0, _tabCumulativeOutputTokens.GetValueOrDefault(_activeTab))); } private void TrackStreamingConversation(string tab, ChatConversation conversation) @@ -1759,6 +1761,14 @@ public partial class ChatWindow : Window } } + private StreamingGuideVisibility GetGuideVisibilityForActiveTab() + { + var isOwningTab = _streamingTabs.Contains(_activeTab); + return ChatStreamingUiPolicy.ResolveGuideVisibility( + isOwningTab, + isOwningTab && IsStreamingConversationVisible(_activeTab)); + } + private void ClearStreamingConversation(string tab, ChatConversation? expectedConversation = null) { var normalizedTab = NormalizeTabName(tab);