From 23b235263727037db20081d520c153feb2c781e4 Mon Sep 17 00:00:00 2001 From: lacvet Date: Tue, 7 Apr 2026 08:03:37 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B1=84=ED=8C=85=20=ED=83=AD=20SSE=20?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A6=AC=EB=B0=8D=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=B3=B5=EA=B5=AC=20=EB=B0=8F=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Chat 탭 직접 대화 경로가 최종 응답만 한 번에 표시하던 문제를 수정하고 LlmService 스트리밍 경로를 실제 UI에 연결함\n- AxAgentExecutionEngine에서 비에이전트 채팅이 스트리밍 전송을 사용할 수 있도록 실행 모드를 조정함\n- ChatWindow에서 기존 스트리밍 컨테이너와 타이핑 타이머를 실제 전송 루프에 연결해 타자 치듯 점진적으로 응답이 보이게 함\n- README와 DEVELOPMENT 문서에 2026-04-07 02:23 (KST) 기준 변경 이력 반영\n- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0 / 오류 0) --- README.md | 4 ++ docs/DEVELOPMENT.md | 2 + .../Services/Agent/AxAgentExecutionEngine.cs | 2 +- src/AxCopilot/Views/ChatWindow.xaml.cs | 52 ++++++++++++++++--- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a69f622..e5446e3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-07 02:23 (KST) +- AX Agent 직접 대화(Chat 탭) 경로에 실제 스트리밍 응답 연결을 복구했습니다. LLM 서비스는 원래 SSE/스트리밍을 지원하고 있었지만 UI 실행 경로가 최종 문자열만 받아 한 번에 붙이던 상태였고, 이제 설정상 스트리밍이 켜져 있으면 채팅 응답이 타자 치듯 점진적으로 표시됩니다. +- Cowork/Code는 기존처럼 agent loop 진행 메시지 중심을 유지하고, 직접 대화 재생성은 같은 스트리밍 경로를 공유하도록 정리했습니다. + - 업데이트: 2026-04-07 02:03 (KST) - Cowork 진행 표시가 오래 비거나 완료 후 실패처럼 깜박이던 흐름을 정리했습니다. 진행 힌트는 작업 시작 직후부터 즉시 보이게 하고, 중간 이벤트가 들어와도 불필요하게 사라지지 않도록 유지 로직을 조정했습니다. - 프리셋/입력창/진행 카드에 남아 있던 깨진 한글을 복구했고, 내부 실행 게이트 문구도 정상 한국어로 정리해 Cowork 루프가 잘못된 안내 문구에 끌리지 않도록 보강했습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index ea5c558..4b6178f 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5284,3 +5284,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - 내부 중단/취소가 발생했을 때 사용자 취소로 단정하는 문구를 중립적으로 조정해 오탐성 `사용자가 작업을 취소했습니다` 표시를 줄였다. +- Document update: 2026-04-07 02:23 (KST) - Reconnected the AX Agent direct-chat execution path to the existing SSE/streaming transport in `LlmService`. Chat replies no longer wait for the final full string before rendering; when streaming is enabled they now advance through the existing streaming container and typing-timer path. +- Document update: 2026-04-07 02:23 (KST) - Updated `AxAgentExecutionEngine.ResolveExecutionMode()` so non-agent chat can opt into streaming transport, and wired `ChatWindow.ExecutePreparedTurnAsync()` to consume `LlmService.StreamAsync(...)` for direct conversations while keeping Cowork/Code on the agent-loop progress-feed path. diff --git a/src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs b/src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs index 1babf37..ddb7ded 100644 --- a/src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs +++ b/src/AxCopilot/Services/Agent/AxAgentExecutionEngine.cs @@ -49,7 +49,7 @@ public sealed class AxAgentExecutionEngine if (string.Equals(runTab, "Code", StringComparison.OrdinalIgnoreCase)) return new ExecutionMode(true, false, codeSystemPrompt); - return new ExecutionMode(false, false, null); + return new ExecutionMode(false, streamingEnabled, null); } public PreparedExecution PrepareExecution( diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index 552b592..09ad1d0 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -5593,14 +5593,45 @@ public partial class ChatWindow : Window _elapsedTimer.Start(); SetStatus(busyStatus, spinning: true); + StackPanel? streamingContainer = null; + TextBlock? streamingText = null; + 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; + if (!preparedExecution.Mode.UseAgentLoop && preparedExecution.Mode.UseStreamingTransport) + { + streamingContainer = CreateStreamingContainer(out var createdStreamText); + streamingText = createdStreamText; + _activeStreamText = streamingText; + _cachedStreamContent = ""; + _displayedLength = 0; + _cursorVisible = true; + MessagePanel.Children.Add(streamingContainer); + ForceScrollToEnd(); + _cursorTimer.Start(); + _typingTimer.Start(); + + await foreach (var chunk in _llm.StreamAsync(preparedExecution.Messages.ToList(), _streamCts.Token)) + { + if (string.IsNullOrEmpty(chunk)) + continue; + + assistantContent += chunk; + _cachedStreamContent = assistantContent; + if (_activeStreamText != null && _displayedLength == 0) + _activeStreamText.Text = _cursorVisible ? "\u258c" : " "; + } + } + else + { + 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; + } + responseElapsedMs = Math.Max(0, (long)(DateTime.UtcNow - _streamStartTime).TotalMilliseconds); assistantMetaRunId = _appState.AgentRun.RunId; var usage = _llm.LastTokenUsage; @@ -5618,8 +5649,11 @@ public partial class ChatWindow : Window } } StopAiIconPulse(); - _cachedStreamContent = response; + _cachedStreamContent = assistantContent; draftSucceeded = true; + + if (streamingContainer != null && streamingText != null) + FinalizeStreamingContainer(streamingContainer, streamingText, assistantContent); } catch (OperationCanceledException) { @@ -5627,6 +5661,8 @@ public partial class ChatWindow : Window assistantContent = finalized.Content; draftCancelled = finalized.Cancelled; draftFailure = finalized.FailureReason; + if (streamingContainer != null && streamingText != null) + FinalizeStreamingContainer(streamingContainer, streamingText, assistantContent); } catch (Exception ex) { @@ -5634,6 +5670,8 @@ public partial class ChatWindow : Window assistantContent = finalized.Content; draftFailure = finalized.FailureReason; ShowToast("실패한 요청은 작업 요약에서 다시 시도할 수 있습니다.", "\uE783", 2600); + if (streamingContainer != null && streamingText != null) + FinalizeStreamingContainer(streamingContainer, streamingText, assistantContent); } finally {