- execution event와 agent run 기록이 들어올 때마다 즉시 저장하지 않고 conversationPersistTimer로 220ms 단위 지연 저장하도록 변경함 - Cowork/Code 연속 이벤트 구간의 디스크 I/O를 줄여 체감 끊김과 잦은 저장 부하를 낮추는 방향으로 정리함 - 검증: 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:
@@ -744,6 +744,7 @@ ow + toggle 시각 언어로 통일했습니다.
|
|||||||
- 이어서 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 실행 후처리도 `ResetStreamingUiState()`, `FinalizeConversationTurn()`, `FinalizeQueuedDraft()`로 묶었습니다. 전송과 재생성이 같은 정리 경로를 공유하게 해서, 응답 완료 뒤 상태 복구와 대화 저장, 대기열 완료/실패 처리 흐름도 더 한 축으로 정리했습니다.
|
- 이어서 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 실행 후처리도 `ResetStreamingUiState()`, `FinalizeConversationTurn()`, `FinalizeQueuedDraft()`로 묶었습니다. 전송과 재생성이 같은 정리 경로를 공유하게 해서, 응답 완료 뒤 상태 복구와 대화 저장, 대기열 완료/실패 처리 흐름도 더 한 축으로 정리했습니다.
|
||||||
- 이번엔 `OnAgentEvent(...)`의 본문 재렌더를 배치형으로 바꿨습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `DispatcherTimer` 기반 `ScheduleExecutionHistoryRender()`를 추가해서, Cowork/Code 실행 중 이벤트가 연속으로 들어와도 `RenderMessages()`가 매 이벤트마다 바로 돌지 않고 짧게 묶여 한 번씩만 반영됩니다.
|
- 이번엔 `OnAgentEvent(...)`의 본문 재렌더를 배치형으로 바꿨습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `DispatcherTimer` 기반 `ScheduleExecutionHistoryRender()`를 추가해서, Cowork/Code 실행 중 이벤트가 연속으로 들어와도 `RenderMessages()`가 매 이벤트마다 바로 돌지 않고 짧게 묶여 한 번씩만 반영됩니다.
|
||||||
- 같은 흐름으로 작업 요약 스트립도 배치형 갱신으로 바꿨습니다. `UpdateTaskSummaryIndicators()`를 즉시 호출하는 대신 `ScheduleTaskSummaryRefresh()`가 120ms 단위로 상태 반영을 묶어, 실행 중 상단 상태 스트립과 런타임 배지가 과하게 흔들리지 않도록 정리했습니다.
|
- 같은 흐름으로 작업 요약 스트립도 배치형 갱신으로 바꿨습니다. `UpdateTaskSummaryIndicators()`를 즉시 호출하는 대신 `ScheduleTaskSummaryRefresh()`가 120ms 단위로 상태 반영을 묶어, 실행 중 상단 상태 스트립과 런타임 배지가 과하게 흔들리지 않도록 정리했습니다.
|
||||||
|
- 추가로 실행 이벤트/실행 기록 저장도 지연 저장으로 바꿨습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AppendConversationExecutionEvent()`와 `AppendConversationAgentRun()`은 이제 이벤트마다 바로 `_storage.Save(...)`를 호출하지 않고, `ScheduleConversationPersist()`를 통해 220ms 단위로 묶어서 flush 합니다. Cowork/Code의 연속 이벤트 구간에서 저장 I/O가 덜 붙도록 만든 조정입니다.
|
||||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
|
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
|
||||||
- 업데이트: 2026-04-05 12:24 (KST)
|
- 업데이트: 2026-04-05 12:24 (KST)
|
||||||
- 업데이트: 2026-04-05 12:31 (KST)
|
- 업데이트: 2026-04-05 12:31 (KST)
|
||||||
@@ -752,6 +753,7 @@ ow + toggle 시각 언어로 통일했습니다.
|
|||||||
- 업데이트: 2026-04-05 12:47 (KST)
|
- 업데이트: 2026-04-05 12:47 (KST)
|
||||||
- 업데이트: 2026-04-05 12:53 (KST)
|
- 업데이트: 2026-04-05 12:53 (KST)
|
||||||
- 업데이트: 2026-04-05 12:58 (KST)
|
- 업데이트: 2026-04-05 12:58 (KST)
|
||||||
|
- 업데이트: 2026-04-05 13:03 (KST)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4507,4 +4507,7 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
|||||||
- 업데이트: 2026-04-05 12:58 (KST)
|
- 업데이트: 2026-04-05 12:58 (KST)
|
||||||
- 같은 파일에 `_taskSummaryRefreshTimer`와 `ScheduleTaskSummaryRefresh()`도 추가해, `UpdateTaskSummaryIndicators()`가 실행 이벤트나 서브에이전트 상태 변경마다 즉시 도는 대신 120ms 단위로 묶여 반영되게 했습니다.
|
- 같은 파일에 `_taskSummaryRefreshTimer`와 `ScheduleTaskSummaryRefresh()`도 추가해, `UpdateTaskSummaryIndicators()`가 실행 이벤트나 서브에이전트 상태 변경마다 즉시 도는 대신 120ms 단위로 묶여 반영되게 했습니다.
|
||||||
- 이 조정은 `RuntimeActivityBadge`, `ConversationStatusStrip`, 완료 요약 라벨 같은 상태 스트립이 빠르게 깜빡이는 체감을 줄이기 위한 것이며, 본문 재렌더 배치와 함께 Cowork/Code 실행 중 전체 UI 안정성을 높이는 단계입니다.
|
- 이 조정은 `RuntimeActivityBadge`, `ConversationStatusStrip`, 완료 요약 라벨 같은 상태 스트립이 빠르게 깜빡이는 체감을 줄이기 위한 것이며, 본문 재렌더 배치와 함께 Cowork/Code 실행 중 전체 UI 안정성을 높이는 단계입니다.
|
||||||
|
- 업데이트: 2026-04-05 13:03 (KST)
|
||||||
|
- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `_conversationPersistTimer`, `ScheduleConversationPersist()`, `FlushPendingConversationPersists()`를 추가해, 실행 이벤트와 실행 기록이 들어올 때마다 곧바로 `_storage.Save(...)`를 치지 않도록 바꿨습니다.
|
||||||
|
- `AppendConversationExecutionEvent()`와 `AppendConversationAgentRun()`는 이제 `ChatSessionStateService`의 append 메서드를 `storage=null`로 호출하고, 변경된 `ChatConversation`만 220ms 단위로 지연 저장합니다. 이 변경은 Cowork/Code 실행 중 빈번한 이벤트가 들어올 때 디스크 I/O 때문에 체감이 끊기는 문제를 줄이기 위한 배치 저장 단계입니다.
|
||||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0
|
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\` 경고 0 / 오류 0
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ public partial class ChatWindow : Window
|
|||||||
private readonly DispatcherTimer _inputUiRefreshTimer;
|
private readonly DispatcherTimer _inputUiRefreshTimer;
|
||||||
private readonly DispatcherTimer _executionHistoryRenderTimer;
|
private readonly DispatcherTimer _executionHistoryRenderTimer;
|
||||||
private readonly DispatcherTimer _taskSummaryRefreshTimer;
|
private readonly DispatcherTimer _taskSummaryRefreshTimer;
|
||||||
|
private readonly DispatcherTimer _conversationPersistTimer;
|
||||||
private CancellationTokenSource? _gitStatusRefreshCts;
|
private CancellationTokenSource? _gitStatusRefreshCts;
|
||||||
private int _displayedLength; // 현재 화면에 표시된 글자 수
|
private int _displayedLength; // 현재 화면에 표시된 글자 수
|
||||||
private ResourceDictionary? _agentThemeDictionary;
|
private ResourceDictionary? _agentThemeDictionary;
|
||||||
@@ -119,6 +120,7 @@ public partial class ChatWindow : Window
|
|||||||
private int _sessionPostCompactionPromptTokens;
|
private int _sessionPostCompactionPromptTokens;
|
||||||
private int _sessionPostCompactionCompletionTokens;
|
private int _sessionPostCompactionCompletionTokens;
|
||||||
private bool _pendingExecutionHistoryAutoScroll;
|
private bool _pendingExecutionHistoryAutoScroll;
|
||||||
|
private readonly Dictionary<string, ChatConversation> _pendingConversationPersists = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg)
|
private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg)
|
||||||
{
|
{
|
||||||
if (button?.Content is not string text)
|
if (button?.Content is not string text)
|
||||||
@@ -251,6 +253,12 @@ public partial class ChatWindow : Window
|
|||||||
_taskSummaryRefreshTimer.Stop();
|
_taskSummaryRefreshTimer.Stop();
|
||||||
UpdateTaskSummaryIndicators();
|
UpdateTaskSummaryIndicators();
|
||||||
};
|
};
|
||||||
|
_conversationPersistTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(220) };
|
||||||
|
_conversationPersistTimer.Tick += (_, _) =>
|
||||||
|
{
|
||||||
|
_conversationPersistTimer.Stop();
|
||||||
|
FlushPendingConversationPersists();
|
||||||
|
};
|
||||||
|
|
||||||
KeyDown += ChatWindow_KeyDown;
|
KeyDown += ChatWindow_KeyDown;
|
||||||
UpdateConversationFailureFilterUi();
|
UpdateConversationFailureFilterUi();
|
||||||
@@ -8557,6 +8565,7 @@ public partial class ChatWindow : Window
|
|||||||
_executionHistoryRenderTimer.Stop();
|
_executionHistoryRenderTimer.Stop();
|
||||||
_pendingExecutionHistoryAutoScroll = false;
|
_pendingExecutionHistoryAutoScroll = false;
|
||||||
_taskSummaryRefreshTimer.Stop();
|
_taskSummaryRefreshTimer.Stop();
|
||||||
|
_conversationPersistTimer.Stop();
|
||||||
HideStickyProgress();
|
HideStickyProgress();
|
||||||
StopRainbowGlow();
|
StopRainbowGlow();
|
||||||
_activeStreamText = null;
|
_activeStreamText = null;
|
||||||
@@ -8626,6 +8635,30 @@ public partial class ChatWindow : Window
|
|||||||
_taskSummaryRefreshTimer.Start();
|
_taskSummaryRefreshTimer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ScheduleConversationPersist(ChatConversation? conversation)
|
||||||
|
{
|
||||||
|
if (conversation == null || string.IsNullOrWhiteSpace(conversation.Id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_pendingConversationPersists[conversation.Id] = conversation;
|
||||||
|
_conversationPersistTimer.Stop();
|
||||||
|
_conversationPersistTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushPendingConversationPersists()
|
||||||
|
{
|
||||||
|
if (_pendingConversationPersists.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var conversation in _pendingConversationPersists.Values.ToList())
|
||||||
|
{
|
||||||
|
try { _storage.Save(conversation); }
|
||||||
|
catch (Exception ex) { Services.LogService.Debug($"대화 지연 저장 실패: {ex.Message}"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingConversationPersists.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// ─── 코워크 에이전트 지원 ────────────────────────────────────────────
|
// ─── 코워크 에이전트 지원 ────────────────────────────────────────────
|
||||||
|
|
||||||
private string BuildCoworkSystemPrompt()
|
private string BuildCoworkSystemPrompt()
|
||||||
@@ -9106,15 +9139,18 @@ public partial class ChatWindow : Window
|
|||||||
|
|
||||||
var normalizedTarget = NormalizeTabName(targetTab);
|
var normalizedTarget = NormalizeTabName(targetTab);
|
||||||
var normalizedActive = NormalizeTabName(_activeTab);
|
var normalizedActive = NormalizeTabName(_activeTab);
|
||||||
|
ChatConversation updatedConversation;
|
||||||
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_currentConversation = session.AppendAgentRun(normalizedTarget, evt, status, summary, _storage);
|
_currentConversation = updatedConversation = session.AppendAgentRun(normalizedTarget, evt, status, summary, null);
|
||||||
|
ScheduleConversationPersist(updatedConversation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var activeSnapshot = _currentConversation;
|
var activeSnapshot = _currentConversation;
|
||||||
var previousSessionConversation = session.CurrentConversation;
|
var previousSessionConversation = session.CurrentConversation;
|
||||||
session.AppendAgentRun(normalizedTarget, evt, status, summary, _storage);
|
updatedConversation = session.AppendAgentRun(normalizedTarget, evt, status, summary, null);
|
||||||
|
ScheduleConversationPersist(updatedConversation);
|
||||||
|
|
||||||
if (activeSnapshot != null && string.Equals(NormalizeTabName(activeSnapshot.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
if (activeSnapshot != null && string.Equals(NormalizeTabName(activeSnapshot.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -9156,15 +9192,18 @@ public partial class ChatWindow : Window
|
|||||||
|
|
||||||
var normalizedTarget = NormalizeTabName(targetTab);
|
var normalizedTarget = NormalizeTabName(targetTab);
|
||||||
var normalizedActive = NormalizeTabName(_activeTab);
|
var normalizedActive = NormalizeTabName(_activeTab);
|
||||||
|
ChatConversation updatedConversation;
|
||||||
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(normalizedTarget, normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_currentConversation = session.AppendExecutionEvent(normalizedTarget, evt, _storage);
|
_currentConversation = updatedConversation = session.AppendExecutionEvent(normalizedTarget, evt, null);
|
||||||
|
ScheduleConversationPersist(updatedConversation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var activeSnapshot = _currentConversation;
|
var activeSnapshot = _currentConversation;
|
||||||
var previousSessionConversation = session.CurrentConversation;
|
var previousSessionConversation = session.CurrentConversation;
|
||||||
session.AppendExecutionEvent(normalizedTarget, evt, _storage);
|
updatedConversation = session.AppendExecutionEvent(normalizedTarget, evt, null);
|
||||||
|
ScheduleConversationPersist(updatedConversation);
|
||||||
|
|
||||||
if (activeSnapshot != null && string.Equals(NormalizeTabName(activeSnapshot.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
if (activeSnapshot != null && string.Equals(NormalizeTabName(activeSnapshot.Tab), normalizedActive, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user