AX Agent 중간 처리 메시지를 Claude Code 스타일에 가깝게 정리
Some checks failed
Release Gate / gate (push) Has been cancelled

- 진행 중 이벤트(Planning, StepStart, StepDone, Thinking, ToolCall, SkillCall)를 작은 상태 칩 대신 요약줄+본문 설명 구조로 재구성했습니다.

- claw-code의 AssistantToolUseMessage/HookProgressMessage 흐름을 참고해 중간 처리 과정이 hover 없이도 transcript 안에서 읽히도록 조정했습니다.

- 권한 요청 및 결과 카드는 기존 richer card 구조를 유지하고, process feed 이벤트만 별도 경량 표현으로 분리했습니다.

- README와 DEVELOPMENT 문서에 2026-04-06 22:31 (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:
2026-04-06 22:25:30 +09:00
parent f48e598cc1
commit 4992dca74f
3 changed files with 152 additions and 67 deletions

View File

@@ -14,12 +14,12 @@ public partial class ChatWindow
{
return new Border
{
Background = hintBg,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(999),
Padding = new Thickness(8, 4, 8, 4),
Margin = new Thickness(8, 3, 180, 3),
Background = Brushes.Transparent,
BorderBrush = Brushes.Transparent,
BorderThickness = new Thickness(0),
CornerRadius = new CornerRadius(0),
Padding = new Thickness(0),
Margin = new Thickness(12, 4, 12, 2),
HorizontalAlignment = HorizontalAlignment.Left,
Child = new StackPanel
{
@@ -28,26 +28,144 @@ public partial class ChatWindow
{
new TextBlock
{
Text = "\uE9CE",
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 9.5,
Foreground = accentBrush,
Text = "",
FontSize = 12,
Foreground = secondaryText,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 6, 0),
},
new TextBlock
{
Text = summary,
FontSize = 9.5,
FontWeight = FontWeights.Medium,
Foreground = primaryText,
Margin = new Thickness(5, 0, 0, 0),
FontSize = 11,
FontWeight = FontWeights.Normal,
Foreground = secondaryText,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.Wrap,
}
}
}
};
}
private Border CreateProcessFeedBody(string text, Brush primaryText)
{
return new Border
{
Background = Brushes.Transparent,
BorderBrush = Brushes.Transparent,
BorderThickness = new Thickness(0),
Padding = new Thickness(0),
Margin = new Thickness(26, 3, 12, 8),
Child = new TextBlock
{
Text = text,
FontSize = 13.5,
FontWeight = FontWeights.Medium,
Foreground = primaryText,
TextWrapping = TextWrapping.Wrap,
}
};
}
private static bool IsProcessFeedEvent(AgentEvent evt)
{
return evt.Type is AgentEventType.Planning
or AgentEventType.StepStart
or AgentEventType.StepDone
or AgentEventType.Thinking
or AgentEventType.ToolCall
or AgentEventType.SkillCall;
}
private static string BuildProcessFeedSummary(AgentEvent evt, string transcriptBadgeLabel, string itemDisplayName)
{
return evt.Type switch
{
AgentEventType.Planning when evt.Steps is { Count: > 0 }
=> $"{evt.Steps.Count}단계 계획 정리",
AgentEventType.StepStart when evt.StepTotal > 0
=> $"{evt.StepCurrent}/{evt.StepTotal} 단계 진행",
AgentEventType.StepDone when evt.StepTotal > 0
=> $"{evt.StepCurrent}/{evt.StepTotal} 단계 완료",
AgentEventType.Thinking when !string.IsNullOrWhiteSpace(evt.Summary)
=> evt.Summary,
AgentEventType.ToolCall
=> string.IsNullOrWhiteSpace(itemDisplayName)
? $"{transcriptBadgeLabel} 실행"
: $"{itemDisplayName} 실행",
AgentEventType.SkillCall
=> string.IsNullOrWhiteSpace(itemDisplayName)
? "스킬 실행"
: $"{itemDisplayName} 실행",
_ => string.IsNullOrWhiteSpace(evt.Summary) ? transcriptBadgeLabel : evt.Summary,
};
}
private void AddProcessFeedMessage(AgentEvent evt, string transcriptBadgeLabel, string itemDisplayName, string? eventSummaryText)
{
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray;
var hintBg = TryFindResource("HintBackground") as Brush
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
var summary = BuildProcessFeedSummary(evt, transcriptBadgeLabel, itemDisplayName).Trim();
if (string.IsNullOrWhiteSpace(summary))
summary = transcriptBadgeLabel;
var stack = new StackPanel
{
Margin = new Thickness(0),
};
var summaryRow = CreateCompactEventPill(summary, primaryText, secondaryText, hintBg, borderBrush, accentBrush);
summaryRow.Opacity = 0;
summaryRow.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(140)));
stack.Children.Add(summaryRow);
var body = (eventSummaryText ?? string.Empty).Trim();
if (!string.IsNullOrWhiteSpace(body)
&& !string.Equals(body, summary, StringComparison.OrdinalIgnoreCase))
{
var bodyBlock = CreateProcessFeedBody(body, primaryText);
bodyBlock.Opacity = 0;
bodyBlock.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(180)));
stack.Children.Add(bodyBlock);
}
if (!string.IsNullOrWhiteSpace(evt.FilePath))
{
var compactPathRow = new StackPanel
{
Orientation = Orientation.Horizontal,
Margin = new Thickness(26, 0, 12, 8),
ToolTip = evt.FilePath,
};
compactPathRow.Children.Add(new TextBlock
{
Text = "\uE8B7",
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 9,
Foreground = secondaryText,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 4, 0),
});
compactPathRow.Children.Add(new TextBlock
{
Text = System.IO.Path.GetFileName(evt.FilePath),
FontSize = 10.5,
Foreground = secondaryText,
VerticalAlignment = VerticalAlignment.Center,
TextTrimming = TextTrimming.CharacterEllipsis,
});
stack.Children.Add(compactPathRow);
}
MessagePanel.Children.Add(stack);
}
private Border CreateAgentMetaChip(string text, string icon, Brush foreground, Brush background, Brush borderBrush)
{
return new Border
@@ -422,59 +540,6 @@ public partial class ChatWindow
{
var logLevel = _settings.Settings.Llm.AgentLogLevel;
if (evt.Type == AgentEventType.Planning && evt.Steps is { Count: > 0 })
{
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;
}
if (evt.Type == AgentEventType.StepStart && evt.StepTotal > 0)
{
UpdateProgressBar(evt);
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 stepSummary = !string.IsNullOrWhiteSpace(evt.Summary)
? evt.Summary!
: $"단계 진행 중 ({evt.StepCurrent}/{evt.StepTotal})";
var pill = CreateCompactEventPill(stepSummary, compactPrimaryText, compactSecondaryText, compactHintBg, compactBorderBrush, compactAccentBrush);
pill.Opacity = 0;
pill.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160)));
MessagePanel.Children.Add(pill);
return;
}
if (evt.Type == AgentEventType.Thinking &&
ContainsAny(evt.Summary ?? "", "컨텍스트 압축", "microcompact", "session memory", "compact"))
{
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 pill = CreateCompactEventPill(string.IsNullOrWhiteSpace(evt.Summary) ? "컨텍스트 압축" : evt.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;
}
if (!string.Equals(logLevel, "debug", StringComparison.OrdinalIgnoreCase)
&& evt.Type is AgentEventType.Paused or AgentEventType.Resumed)
return;
@@ -524,6 +589,15 @@ public partial class ChatWindow
};
}
if (evt.Type == AgentEventType.StepStart && evt.StepTotal > 0)
UpdateProgressBar(evt);
if (IsProcessFeedEvent(evt))
{
AddProcessFeedMessage(evt, transcriptBadgeLabel, itemDisplayName, eventSummaryText);
return;
}
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray;
var hintBg = TryFindResource("HintBackground") as Brush ?? BrushFromHex("#F8FAFC");