AX Agent 도구·스킬 정합성 재구성 및 실행 품질 보강

변경 목적:
- AX Agent의 도구 이름, 내부 설정, 스킬 정책, 실행 루프 사이의 불일치를 줄이고 전체 동작 품질을 높인다.
- claw-code 수준의 일관된 동작 품질을 참고하되 AX 구조에 맞는 고유한 카탈로그·정규화 레이어로 재구성한다.

핵심 수정사항:
- 도구 canonical id, legacy alias, 탭 노출, 설정 카테고리, read-only 분류를 중앙 카탈로그로 통합했다.
- ToolRegistry, AgentLoopService, 병렬 실행 분류, 권한 처리, 훅 처리, 스킬 allowed-tools 해석이 같은 이름 체계를 사용하도록 정리했다.
- Agent 설정/일반 설정/도움말의 도구 카드와 훅 편집기, 스킬 설명을 현재 런타임 구조에 맞게 갱신했다.
- 컨텍스트 압축, intent gate, spawn agents, session learning, model prompt adapter, workspace context 관련 변경과 테스트 추가를 함께 반영했다.
- 문서 이력과 비교/로드맵 문서를 최신 상태로 갱신했다.

검증 결과:
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_toolcat\ -p:IntermediateOutputPath=obj\verify_toolcat\ : 경고 0 / 오류 0
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentToolCatalogTests -p:OutputPath=bin\verify_toolcat_tests\ -p:IntermediateOutputPath=obj\verify_toolcat_tests\ : 통과 8
This commit is contained in:
2026-04-14 17:52:46 +09:00
parent fa33b98f7e
commit 8cb08576d5
200 changed files with 13522 additions and 5764 deletions

View File

@@ -19,7 +19,7 @@ internal sealed class ToolApprovalWindow : Window
private ToolApprovalWindow(string message, List<string> options)
{
Width = 480;
Width = 500;
MinWidth = 400;
MaxWidth = 600;
SizeToContent = SizeToContent.Height;
@@ -40,6 +40,10 @@ internal sealed class ToolApprovalWindow : Window
var border = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var itemBg = Application.Current.TryFindResource("ItemBackground") as Brush
?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40));
var errorBrush = Application.Current.TryFindResource("ErrorColor") as Brush
?? new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
var hoverBg = Application.Current.TryFindResource("ItemHoverBackground") as Brush
?? new SolidColorBrush(Color.FromArgb(0x20, 0xFF, 0xFF, 0xFF));
var root = new Border
{
@@ -108,7 +112,7 @@ internal sealed class ToolApprovalWindow : Window
},
};
close.MouseLeftButtonUp += (_, _) => { _result = "취소"; Close(); };
close.MouseEnter += (_, _) => close.Background = new SolidColorBrush(Color.FromArgb(0x20, 0xFF, 0xFF, 0xFF));
close.MouseEnter += (_, _) => close.Background = hoverBg;
close.MouseLeave += (_, _) => close.Background = Brushes.Transparent;
header.Children.Add(close);
stack.Children.Add(header);
@@ -117,7 +121,7 @@ internal sealed class ToolApprovalWindow : Window
var msgBorder = new Border
{
Background = itemBg,
CornerRadius = new CornerRadius(10),
CornerRadius = new CornerRadius(12),
Padding = new Thickness(14, 11, 14, 11),
Margin = new Thickness(0, 0, 0, 14),
};
@@ -125,11 +129,11 @@ internal sealed class ToolApprovalWindow : Window
var msgText = new TextBlock
{
Text = message,
FontSize = 12.5,
FontSize = 13,
FontFamily = new FontFamily("Segoe UI"),
Foreground = primary,
TextWrapping = TextWrapping.Wrap,
LineHeight = 19,
LineHeight = 20,
};
msgBorder.Child = msgText;
stack.Children.Add(msgBorder);
@@ -143,7 +147,7 @@ internal sealed class ToolApprovalWindow : Window
foreach (var option in options)
{
var btn = CreateOptionButton(option, primary, secondary, accent, bg);
var btn = CreateOptionButton(option, primary, secondary, accent, bg, errorBrush);
btn.MouseLeftButtonUp += (_, _) => { _result = option; Close(); };
btnPanel.Children.Add(btn);
}
@@ -182,7 +186,7 @@ internal sealed class ToolApprovalWindow : Window
};
}
private Border CreateOptionButton(string label, Brush primary, Brush secondary, Brush accent, Brush bg)
private Border CreateOptionButton(string label, Brush primary, Brush secondary, Brush accent, Brush bg, Brush errorBrush)
{
Brush foreground, background, borderBrush;
switch (label)
@@ -195,9 +199,9 @@ internal sealed class ToolApprovalWindow : Window
break;
case "취소":
case "중단":
foreground = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
foreground = errorBrush;
background = Brushes.Transparent;
borderBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
borderBrush = errorBrush;
break;
default:
foreground = primary;
@@ -206,18 +210,26 @@ internal sealed class ToolApprovalWindow : Window
break;
}
var btn = new Border
var isDestructive = label == "취소" || label == "중단";
// Build inner content: optional left accent bar + label
FrameworkElement child;
if (isDestructive)
{
MinWidth = 70,
Height = 32,
CornerRadius = new CornerRadius(8),
Background = background,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(14, 0, 14, 0),
Margin = new Thickness(6, 0, 0, 0),
Cursor = Cursors.Hand,
Child = new TextBlock
var grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
var accentBar = new Border
{
Width = 4,
CornerRadius = new CornerRadius(2),
Background = errorBrush,
Margin = new Thickness(0, 4, 8, 4),
VerticalAlignment = VerticalAlignment.Stretch,
};
Grid.SetColumn(accentBar, 0);
grid.Children.Add(accentBar);
var txt = new TextBlock
{
Text = label,
FontSize = 12,
@@ -225,7 +237,36 @@ internal sealed class ToolApprovalWindow : Window
Foreground = foreground,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
},
};
Grid.SetColumn(txt, 1);
grid.Children.Add(txt);
child = grid;
}
else
{
child = new TextBlock
{
Text = label,
FontSize = 12,
FontWeight = FontWeights.SemiBold,
Foreground = foreground,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
}
var btn = new Border
{
MinWidth = 84,
Height = 36,
CornerRadius = new CornerRadius(10),
Background = background,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(14, 0, 14, 0),
Margin = new Thickness(10, 0, 0, 0),
Cursor = Cursors.Hand,
Child = child,
};
btn.MouseEnter += (_, _) => btn.Opacity = 0.85;
@@ -236,6 +277,13 @@ internal sealed class ToolApprovalWindow : Window
/// <summary>도구 승인 다이얼로그를 표시하고 결과를 반환합니다.</summary>
internal static string? Show(Window? owner, string message, List<string> options)
=> Show(owner, message, options, CancellationToken.None);
/// <summary>
/// 도구 승인 다이얼로그를 표시합니다. cancellationToken이 트리거되면 창을 자동으로 닫고 null을 반환합니다.
/// 장시간 미응답 시 타임아웃으로 에이전트 루프가 멈추는 것을 방지하기 위해 사용합니다.
/// </summary>
internal static string? Show(Window? owner, string message, List<string> options, CancellationToken cancellationToken)
{
var dialog = new ToolApprovalWindow(message, options);
if (owner != null && IsWindowAlive(owner))
@@ -244,7 +292,29 @@ internal sealed class ToolApprovalWindow : Window
dialog.Owner = owner;
}
dialog.ShowDialog();
CancellationTokenRegistration reg = default;
if (cancellationToken.CanBeCanceled)
{
reg = cancellationToken.Register(() =>
{
// UI 스레드에서 안전하게 닫기
dialog.Dispatcher.BeginInvoke(new Action(() =>
{
try { if (dialog.IsVisible) dialog.Close(); }
catch { /* 이미 닫혀있거나 파괴 중 — 무시 */ }
}));
});
}
try
{
dialog.ShowDialog();
}
finally
{
reg.Dispose();
}
return dialog._result;
}