AX Agent 실행 마감 helper 통합과 계획 노출 최소화
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- send와 regenerate가 같은 실행/예외/취소/최종 커밋/후처리 helper를 타도록 ExecutePreparedTurnAsync 추가 - 계획 이벤트는 기본적으로 얇은 요약 pill만 노출하고 debug에서만 큰 카드가 보이도록 조정 - README와 DEVELOPMENT 문서에 2026-04-05 18:08 (KST) 기준 이력 반영 - 검증: 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:
@@ -833,6 +833,9 @@ ow + toggle 시각 언어로 통일했습니다.
|
|||||||
- 큰 카드형 요소도 더 `claw-code` 쪽으로 눌렀습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AddPlanningCard(...)` 에서 계획 카드 라운드, 패딩, 헤더 아이콘/텍스트, 진행률 텍스트, 단계 행 폰트를 전반적으로 줄였고, 계획 헤더 문구도 더 짧게 정리했습니다.
|
- 큰 카드형 요소도 더 `claw-code` 쪽으로 눌렀습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AddPlanningCard(...)` 에서 계획 카드 라운드, 패딩, 헤더 아이콘/텍스트, 진행률 텍스트, 단계 행 폰트를 전반적으로 줄였고, 계획 헤더 문구도 더 짧게 정리했습니다.
|
||||||
- 같은 변경에서 `CreateCompactEventPill(...)`, `CreateTimelineLoadMoreCard(...)`도 함께 축소해 컨텍스트 압축 pill과 “이전 대화 더 보기” 카드가 본문보다 과하게 두껍게 보이지 않도록 맞췄습니다.
|
- 같은 변경에서 `CreateCompactEventPill(...)`, `CreateTimelineLoadMoreCard(...)`도 함께 축소해 컨텍스트 압축 pill과 “이전 대화 더 보기” 카드가 본문보다 과하게 두껍게 보이지 않도록 맞췄습니다.
|
||||||
- 업데이트: 2026-04-05 18:01 (KST)
|
- 업데이트: 2026-04-05 18:01 (KST)
|
||||||
|
- 엔진 마감도 한 단계 더 진행했습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `ExecutePreparedTurnAsync(...)`를 추가해 `send`와 `regenerate`가 같은 실행/예외/취소/최종 커밋/후처리 경로를 타도록 묶었습니다. 이제 전송과 재생성은 같은 prepared-execution 축에서 닫히고, 실패 토스트와 최종 assistant 커밋도 같은 helper가 담당합니다.
|
||||||
|
- 같은 변경에서 계획 이벤트는 기본적으로 큰 카드가 아니라 얇은 요약 pill로만 보이고, `debug` 로그일 때만 `AddPlanningCard(...)`가 펼쳐지도록 바꿨습니다. 문서형 Cowork/Code 작업에서도 기본 노출이 더 `claw-code`처럼 차분한 상태가 됐습니다.
|
||||||
|
- 업데이트: 2026-04-05 18:08 (KST)
|
||||||
- 좌측 패널과 하단 바도 `claw-code` 쪽 밀도로 다시 맞췄습니다. [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 사이드바 폭을 줄이고, 헤더 앱 배지를 강조색 채운 정사각형 대신 `HintBackground + BorderColor` 기반의 작은 배지형으로 바꿨습니다.
|
- 좌측 패널과 하단 바도 `claw-code` 쪽 밀도로 다시 맞췄습니다. [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 에서 사이드바 폭을 줄이고, 헤더 앱 배지를 강조색 채운 정사각형 대신 `HintBackground + BorderColor` 기반의 작은 배지형으로 바꿨습니다.
|
||||||
- `새 대화`, `검색`, `작업 유형/워크스페이스`, 하단 사용자 영역, 삭제 영역까지 패딩과 폰트, 아이콘 크기를 함께 낮췄고, 하단 상태바는 다이아몬드 아이콘을 작은 원형 점으로 바꿔 더 단순한 상태선처럼 보이게 정리했습니다.
|
- `새 대화`, `검색`, `작업 유형/워크스페이스`, 하단 사용자 영역, 삭제 영역까지 패딩과 폰트, 아이콘 크기를 함께 낮췄고, 하단 상태바는 다이아몬드 아이콘을 작은 원형 점으로 바꿔 더 단순한 상태선처럼 보이게 정리했습니다.
|
||||||
- 실행 로그 배너도 본문 침범을 더 줄였습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AddAgentEventBanner(...)` 에서 debug 전용 `ToolInput` 카드 길이를 더 짧게 줄였고, `FilePath`는 일반 로그에서는 빠른 액션이 붙은 카드형 대신 파일명 한 줄만 약하게 표시하도록 바꿨습니다.
|
- 실행 로그 배너도 본문 침범을 더 줄였습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 `AddAgentEventBanner(...)` 에서 debug 전용 `ToolInput` 카드 길이를 더 짧게 줄였고, `FilePath`는 일반 로그에서는 빠른 액션이 붙은 카드형 대신 파일명 한 줄만 약하게 표시하도록 바꿨습니다.
|
||||||
|
|||||||
@@ -4612,3 +4612,7 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
|||||||
- 같은 변경에서 `CreateCompactEventPill(...)`, `CreateTimelineLoadMoreCard(...)`도 함께 축소해 컨텍스트 압축 pill과 “이전 대화 더 보기” 카드가 본문보다 과하게 두껍게 보이지 않도록 맞췄습니다.
|
- 같은 변경에서 `CreateCompactEventPill(...)`, `CreateTimelineLoadMoreCard(...)`도 함께 축소해 컨텍스트 압축 pill과 “이전 대화 더 보기” 카드가 본문보다 과하게 두껍게 보이지 않도록 맞췄습니다.
|
||||||
- 검증 예정: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`
|
- 검증 예정: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`
|
||||||
- 업데이트: 2026-04-05 18:01 (KST)
|
- 업데이트: 2026-04-05 18:01 (KST)
|
||||||
|
- 엔진 마감도 한 단계 더 진행했습니다. [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)에 `ExecutePreparedTurnAsync(...)`를 추가해 `send`와 `regenerate`가 같은 실행/예외/취소/최종 커밋/후처리 helper를 타도록 묶었습니다. 이로써 prepared-execution 이후 마감 경로도 하나로 수렴했습니다.
|
||||||
|
- 같은 변경에서 계획 이벤트는 기본적으로 큰 카드가 아니라 얇은 요약 pill로만 보이고, `debug` 로그일 때만 `AddPlanningCard(...)`가 펼쳐지도록 바꿨습니다. 문서형 Cowork/Code 작업에서도 기본 노출이 더 최소화됐습니다.
|
||||||
|
- 검증 예정: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\`
|
||||||
|
- 업데이트: 2026-04-05 18:08 (KST)
|
||||||
|
|||||||
@@ -8372,30 +8372,17 @@ public partial class ChatWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = await _chatEngine.ExecutePreparedAsync(
|
var executionOutcome = await ExecutePreparedTurnAsync(
|
||||||
|
conv,
|
||||||
|
runTab,
|
||||||
|
originTab,
|
||||||
preparedExecution,
|
preparedExecution,
|
||||||
(messages, token) => RunAgentLoopAsync(runTab, originTab, conv, messages, token),
|
"응답 생성 중...");
|
||||||
(messages, token) => _llm.SendAsync(messages.ToList(), token),
|
conv = executionOutcome.Conversation;
|
||||||
_streamCts.Token);
|
assistantContent = executionOutcome.AssistantContent;
|
||||||
assistantContent = response;
|
draftSucceeded = executionOutcome.DraftSucceeded;
|
||||||
StopAiIconPulse();
|
draftCancelled = executionOutcome.DraftCancelled;
|
||||||
_cachedStreamContent = response;
|
draftFailure = executionOutcome.DraftFailure;
|
||||||
|
|
||||||
draftSucceeded = true;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, cancelled: true);
|
|
||||||
assistantContent = finalized.Content;
|
|
||||||
draftCancelled = finalized.Cancelled;
|
|
||||||
draftFailure = finalized.FailureReason;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, ex);
|
|
||||||
assistantContent = finalized.Content;
|
|
||||||
ShowToast("실패한 요청은 작업 요약에서 다시 시도할 수 있습니다.", "\uE783", 2600);
|
|
||||||
draftFailure = finalized.FailureReason;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -8405,18 +8392,7 @@ public partial class ChatWindow : Window
|
|||||||
_llm.ClearRouteOverride();
|
_llm.ClearRouteOverride();
|
||||||
UpdateModelLabel();
|
UpdateModelLabel();
|
||||||
}
|
}
|
||||||
ResetStreamingUiState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_convLock)
|
|
||||||
{
|
|
||||||
var session = ChatSession;
|
|
||||||
assistantContent = _chatEngine.FinalizeAssistantTurn(session, conv, runTab, assistantContent, _storage);
|
|
||||||
_currentConversation = session?.CurrentConversation ?? conv;
|
|
||||||
conv = _currentConversation!;
|
|
||||||
}
|
|
||||||
|
|
||||||
FinalizeConversationTurn(originTab, conv);
|
|
||||||
FinalizeQueuedDraft(originTab, queuedDraftId, draftSucceeded, draftCancelled, draftFailure);
|
FinalizeQueuedDraft(originTab, queuedDraftId, draftSucceeded, draftCancelled, draftFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8553,6 +8529,87 @@ public partial class ChatWindow : Window
|
|||||||
_ = Dispatcher.BeginInvoke(new Action(() => StartNextQueuedDraftIfAny()), DispatcherPriority.Background);
|
_ = Dispatcher.BeginInvoke(new Action(() => StartNextQueuedDraftIfAny()), DispatcherPriority.Background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed record PreparedTurnOutcome(
|
||||||
|
ChatConversation Conversation,
|
||||||
|
string AssistantContent,
|
||||||
|
bool DraftSucceeded,
|
||||||
|
bool DraftCancelled,
|
||||||
|
string? DraftFailure);
|
||||||
|
|
||||||
|
private async Task<PreparedTurnOutcome> ExecutePreparedTurnAsync(
|
||||||
|
ChatConversation conversation,
|
||||||
|
string runTab,
|
||||||
|
string rememberTab,
|
||||||
|
AxAgentExecutionEngine.PreparedExecution preparedExecution,
|
||||||
|
string busyStatus)
|
||||||
|
{
|
||||||
|
_isStreaming = true;
|
||||||
|
_streamRunTab = runTab;
|
||||||
|
BtnSend.IsEnabled = false;
|
||||||
|
BtnSend.Visibility = Visibility.Collapsed;
|
||||||
|
BtnStop.Visibility = Visibility.Visible;
|
||||||
|
if (runTab == "Cowork" || runTab == "Code")
|
||||||
|
BtnPause.Visibility = Visibility.Visible;
|
||||||
|
_streamCts = new CancellationTokenSource();
|
||||||
|
ForceScrollToEnd();
|
||||||
|
|
||||||
|
var assistantContent = string.Empty;
|
||||||
|
var draftSucceeded = false;
|
||||||
|
var draftCancelled = false;
|
||||||
|
string? draftFailure = null;
|
||||||
|
|
||||||
|
_activeStreamText = null;
|
||||||
|
_cachedStreamContent = "";
|
||||||
|
_displayedLength = 0;
|
||||||
|
_cursorVisible = true;
|
||||||
|
_aiIconPulseStopped = false;
|
||||||
|
_streamStartTime = DateTime.UtcNow;
|
||||||
|
_elapsedTimer.Start();
|
||||||
|
SetStatus(busyStatus, spinning: true);
|
||||||
|
|
||||||
|
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;
|
||||||
|
StopAiIconPulse();
|
||||||
|
_cachedStreamContent = response;
|
||||||
|
draftSucceeded = true;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, cancelled: true);
|
||||||
|
assistantContent = finalized.Content;
|
||||||
|
draftCancelled = finalized.Cancelled;
|
||||||
|
draftFailure = finalized.FailureReason;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var finalized = _chatEngine.FinalizeExecutionContent(assistantContent, ex);
|
||||||
|
assistantContent = finalized.Content;
|
||||||
|
draftFailure = finalized.FailureReason;
|
||||||
|
ShowToast("실패한 요청은 작업 요약에서 다시 시도할 수 있습니다.", "\uE783", 2600);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ResetStreamingUiState();
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_convLock)
|
||||||
|
{
|
||||||
|
var session = ChatSession;
|
||||||
|
assistantContent = _chatEngine.FinalizeAssistantTurn(session, conversation, runTab, assistantContent, _storage);
|
||||||
|
_currentConversation = session?.CurrentConversation ?? conversation;
|
||||||
|
conversation = _currentConversation!;
|
||||||
|
}
|
||||||
|
|
||||||
|
FinalizeConversationTurn(rememberTab, conversation);
|
||||||
|
return new PreparedTurnOutcome(conversation, assistantContent, draftSucceeded, draftCancelled, draftFailure);
|
||||||
|
}
|
||||||
|
|
||||||
private void ScheduleExecutionHistoryRender(bool autoScroll = true)
|
private void ScheduleExecutionHistoryRender(bool autoScroll = true)
|
||||||
{
|
{
|
||||||
_pendingExecutionHistoryAutoScroll |= autoScroll;
|
_pendingExecutionHistoryAutoScroll |= autoScroll;
|
||||||
@@ -10287,10 +10344,29 @@ public partial class ChatWindow : Window
|
|||||||
{
|
{
|
||||||
var logLevel = _settings.Settings.Llm.AgentLogLevel;
|
var logLevel = _settings.Settings.Llm.AgentLogLevel;
|
||||||
|
|
||||||
// Planning 이벤트는 단계 목록 카드로 별도 렌더링
|
// Planning 이벤트는 기본적으로 얇은 요약만 보이고, debug에서만 큰 카드로 펼칩니다.
|
||||||
if (evt.Type == AgentEventType.Planning && evt.Steps is { Count: > 0 })
|
if (evt.Type == AgentEventType.Planning && evt.Steps is { Count: > 0 })
|
||||||
{
|
{
|
||||||
AddPlanningCard(evt);
|
if (string.Equals(logLevel, "debug", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
AddPlanningCard(evt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var compactPrimaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||||
|
var compactSecondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||||
|
var compactHintBg = TryFindResource("HintBackground") as Brush
|
||||||
|
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||||
|
var compactBorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||||
|
var compactAccentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||||
|
var summary = !string.IsNullOrWhiteSpace(evt.Summary)
|
||||||
|
? evt.Summary!
|
||||||
|
: $"계획 {evt.Steps.Count}단계";
|
||||||
|
var pill = CreateCompactEventPill(summary, compactPrimaryText, compactSecondaryText, compactHintBg, compactBorderBrush, compactAccentBrush);
|
||||||
|
pill.Opacity = 0;
|
||||||
|
pill.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160)));
|
||||||
|
MessagePanel.Children.Add(pill);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10876,64 +10952,15 @@ public partial class ChatWindow : Window
|
|||||||
private async Task SendRegenerateAsync(ChatConversation conv)
|
private async Task SendRegenerateAsync(ChatConversation conv)
|
||||||
{
|
{
|
||||||
var runTab = NormalizeTabName(conv.Tab);
|
var runTab = NormalizeTabName(conv.Tab);
|
||||||
_isStreaming = true;
|
AxAgentExecutionEngine.PreparedExecution preparedExecution;
|
||||||
_streamRunTab = runTab;
|
|
||||||
BtnSend.IsEnabled = false;
|
|
||||||
BtnSend.Visibility = Visibility.Collapsed;
|
|
||||||
BtnStop.Visibility = Visibility.Visible;
|
|
||||||
_streamCts = new CancellationTokenSource();
|
|
||||||
ForceScrollToEnd(); // 응답 시작 시 강제 하단 이동
|
|
||||||
|
|
||||||
var assistantContent = string.Empty;
|
|
||||||
_activeStreamText = null;
|
|
||||||
_cachedStreamContent = "";
|
|
||||||
_displayedLength = 0;
|
|
||||||
_cursorVisible = true;
|
|
||||||
_aiIconPulseStopped = false;
|
|
||||||
_streamStartTime = DateTime.UtcNow;
|
|
||||||
_elapsedTimer.Start();
|
|
||||||
SetStatus("에이전트 작업 중...", spinning: true);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
List<ChatMessage> sendMessages;
|
|
||||||
AxAgentExecutionEngine.PreparedExecution preparedExecution;
|
|
||||||
lock (_convLock)
|
|
||||||
preparedExecution = PrepareExecutionForConversation(conv, runTab, null);
|
|
||||||
var executionMode = preparedExecution.Mode;
|
|
||||||
sendMessages = preparedExecution.Messages;
|
|
||||||
|
|
||||||
var response = await _chatEngine.ExecutePreparedAsync(
|
|
||||||
preparedExecution,
|
|
||||||
(messages, token) => RunAgentLoopAsync(runTab, runTab, conv, messages, token),
|
|
||||||
(messages, token) => _llm.SendAsync(messages.ToList(), token),
|
|
||||||
_streamCts.Token);
|
|
||||||
assistantContent = response;
|
|
||||||
StopAiIconPulse();
|
|
||||||
_cachedStreamContent = response;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
assistantContent = _chatEngine.FinalizeExecutionContent(assistantContent, cancelled: true).Content;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
assistantContent = _chatEngine.FinalizeExecutionContent(assistantContent, ex).Content;
|
|
||||||
ShowToast("실패한 요청은 작업 요약에서 다시 시도할 수 있습니다.", "\uE783", 2600);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ResetStreamingUiState();
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_convLock)
|
lock (_convLock)
|
||||||
{
|
preparedExecution = PrepareExecutionForConversation(conv, runTab, null);
|
||||||
var session = ChatSession;
|
await ExecutePreparedTurnAsync(
|
||||||
assistantContent = _chatEngine.FinalizeAssistantTurn(session, conv, runTab, assistantContent, _storage);
|
conv,
|
||||||
_currentConversation = session?.CurrentConversation ?? conv;
|
runTab,
|
||||||
conv = _currentConversation!;
|
conv.Tab ?? _activeTab,
|
||||||
}
|
preparedExecution,
|
||||||
FinalizeConversationTurn(conv.Tab ?? _activeTab, conv);
|
"에이전트 작업 중...");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>채팅 본문 폭을 세 탭에서 동일한 기준으로 맞춥니다.</summary>
|
/// <summary>채팅 본문 폭을 세 탭에서 동일한 기준으로 맞춥니다.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user