- 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)
This commit is contained in:
@@ -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 루프가 잘못된 안내 문구에 끌리지 않도록 보강했습니다.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user