코워크·코드 장시간 실행 종료 직전 NullReference 실패를 방지하고 예외 추적을 보강한다
Some checks are pending
Release Gate / gate (push) Waiting to run

- ChatWindow 실행 경로에서 스트리밍 취소 토큰을 지역 변수로 고정해 마지막 라이브 프리뷰 단계에서 _streamCts null 참조가 발생하지 않도록 수정

- 최종 타이핑 프리뷰 컨테이너 준비 실패와 취소 예외를 방어적으로 처리해 장시간 작업이 마지막 UI 렌더 때문에 오류로 뒤집히지 않도록 정리

- 에이전트 실행 예외 전체를 앱 로그에 남기고 README 및 DEVELOPMENT 문서 이력을 갱신
This commit is contained in:
2026-04-07 09:26:28 +09:00
parent f34878cbd5
commit 4c8b550242
3 changed files with 26 additions and 6 deletions

View File

@@ -5595,7 +5595,9 @@ public partial class ChatWindow : Window
BtnStop.Visibility = Visibility.Visible;
if (runTab == "Cowork" || runTab == "Code")
BtnPause.Visibility = Visibility.Visible;
_streamCts = new CancellationTokenSource();
var streamCts = new CancellationTokenSource();
_streamCts = streamCts;
var streamToken = streamCts.Token;
ForceScrollToEnd();
var assistantContent = string.Empty;
@@ -5651,8 +5653,8 @@ public partial class ChatWindow : Window
preparedExecution,
(messages, token) => RunAgentLoopAsync(runTab, rememberTab, conversation, messages, token),
(messages, token) => _llm.SendAsync(messages.ToList(), token),
_streamCts.Token);
assistantContent = response;
streamToken);
assistantContent = response ?? string.Empty;
}
responseElapsedMs = Math.Max(0, (long)(DateTime.UtcNow - _streamStartTime).TotalMilliseconds);
@@ -5681,7 +5683,7 @@ public partial class ChatWindow : Window
}
else if (preparedExecution.Mode.UseAgentLoop && !string.IsNullOrWhiteSpace(assistantContent))
{
await ShowTypedAssistantPreviewAsync(assistantContent, _streamCts.Token);
await ShowTypedAssistantPreviewAsync(assistantContent, streamToken);
}
}
catch (OperationCanceledException)
@@ -5695,6 +5697,7 @@ public partial class ChatWindow : Window
}
catch (Exception ex)
{
Services.LogService.Debug($"에이전트 실행 실패: {ex}");
var finalized = _chatEngine.FinalizeExecutionContentForUi(assistantContent, ex);
assistantContent = finalized.Content;
draftFailure = finalized.FailureReason;
@@ -5734,6 +5737,9 @@ public partial class ChatWindow : Window
return;
var container = CreateStreamingContainer(out var streamText);
if (container == null || streamText == null || MessagePanel == null)
return;
_activeStreamText = streamText;
_cachedStreamContent = finalContent;
_displayedLength = 0;
@@ -5745,8 +5751,15 @@ public partial class ChatWindow : Window
streamText.Text = _cursorVisible ? "\u258c" : " ";
var deadline = DateTime.UtcNow.AddMilliseconds(Math.Clamp(finalContent.Length * 6, 500, 1800));
while (_displayedLength < _cachedStreamContent.Length && DateTime.UtcNow < deadline && !ct.IsCancellationRequested)
await Task.Delay(30, ct);
try
{
while (_displayedLength < _cachedStreamContent.Length && DateTime.UtcNow < deadline && !ct.IsCancellationRequested)
await Task.Delay(30, ct);
}
catch (OperationCanceledException)
{
return;
}
_displayedLength = _cachedStreamContent.Length;
if (_activeStreamText != null)