설정 fan-out 통합과 AX Agent 대기열 상태 분리 정리
Some checks failed
Release Gate / gate (push) Has been cancelled

- AX Agent가 SettingsService 변경 이벤트를 직접 구독해 메인 설정과 AX Agent 설정 어느 경로에서 저장하더라도 테마, 모델, 권한, 데이터 활용, composer, 대기열 UI를 즉시 다시 읽어오도록 fan-out 경로를 통합함
- AX Agent 설정 저장 경로와 구형 Agent 설정창에서 표현 수준을 rich로 덮어쓰던 처리를 제거해 풍부하게/적절하게/간단하게 설정이 유지되도록 보정함
- DraftQueue 패널을 실행 중/다음 작업/보류/완료/실패 개별 섹션으로 재구성해 queue state를 더 빠르게 파악할 수 있게 정리함
- README, DEVELOPMENT, AGENT_ROADMAP 문서 이력을 2026-04-04 23:23 (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-04 23:25:12 +09:00
parent 0fa2528401
commit d883ccf9e6
5 changed files with 91 additions and 38 deletions

View File

@@ -383,7 +383,6 @@ public partial class AgentSettingsWindow : Window
_llm.PlanMode = _planMode;
_llm.AgentDecisionLevel = _reasoningMode;
_llm.FolderDataUsage = _folderDataUsage;
_llm.AgentUiExpressionLevel = "rich";
_llm.EnableProactiveContextCompact = ChkEnableProactiveCompact.IsChecked == true;
_llm.ContextCompactTriggerPercent = ParseInt(TxtContextCompactTriggerPercent.Text, 80, 10, 95);

View File

@@ -94,6 +94,7 @@ public partial class ChatWindow : Window
private string _gitBranchSearchText = "";
private StackPanel? _selectedMessageActionBar;
private Border? _selectedMessageBorder;
private bool _isRefreshingFromSettings;
private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg)
{
if (button?.Content is not string text)
@@ -134,6 +135,7 @@ public partial class ChatWindow : Window
{
InitializeComponent();
_settings = settings;
_settings.SettingsChanged += Settings_SettingsChanged;
_storage = new ChatStorageService();
_llm = new LlmService(settings);
_router = new ModelRouterService(settings);
@@ -297,6 +299,7 @@ public partial class ChatWindow : Window
};
Closed += (_, _) =>
{
_settings.SettingsChanged -= Settings_SettingsChanged;
SubAgentTool.StatusChanged -= OnSubAgentStatusChanged;
_streamCts?.Cancel();
_cursorTimer.Stop();
@@ -306,6 +309,28 @@ public partial class ChatWindow : Window
};
}
private void Settings_SettingsChanged(object? sender, EventArgs e)
{
if (_forceClose || !IsLoaded || _isRefreshingFromSettings)
return;
Dispatcher.BeginInvoke(new Action(() =>
{
if (_forceClose || !IsLoaded)
return;
_isRefreshingFromSettings = true;
try
{
RefreshFromSavedSettings();
}
finally
{
_isRefreshingFromSettings = false;
}
}), DispatcherPriority.Input);
}
private bool IsPermissionAutoApprovedForSession(string toolName, string target)
{
if (string.IsNullOrWhiteSpace(toolName) || string.IsNullOrWhiteSpace(target))
@@ -13956,6 +13981,8 @@ public partial class ChatWindow : Window
RefreshContextUsageVisual();
UpdateTabUI();
BuildBottomBar();
RefreshDraftQueueUi();
RefreshConversationList();
}, DispatcherPriority.Input);
}
@@ -13978,7 +14005,6 @@ public partial class ChatWindow : Window
llm.Code.EnableCodeVerification = ChkOverlayEnableCodeVerification?.IsChecked == true;
llm.EnableParallelTools = ChkOverlayEnableParallelTools?.IsChecked == true;
llm.FolderDataUsage = _folderDataUsage;
llm.AgentUiExpressionLevel = "rich";
CommitOverlayEndpointInput(normalizeOnInvalid: true);
CommitOverlayApiKeyInput();
@@ -17228,45 +17254,35 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
}
DraftQueuePanel.Visibility = Visibility.Visible;
const int maxVisible = 8;
var summary = _appState.GetDraftQueueSummary(_activeTab);
var activeItems = visibleItems
.Where(item => !string.Equals(item.State, "completed", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(item.State, "failed", StringComparison.OrdinalIgnoreCase))
.Take(maxVisible)
.ToList();
var historyItems = visibleItems
.Where(item => string.Equals(item.State, "completed", StringComparison.OrdinalIgnoreCase)
|| string.Equals(item.State, "failed", StringComparison.OrdinalIgnoreCase))
.Take(Math.Max(0, maxVisible - activeItems.Count))
.ToList();
DraftQueuePanel.Children.Add(CreateDraftQueueSummaryStrip(summary));
const int maxPerSection = 3;
var runningItems = visibleItems
.Where(item => string.Equals(item.State, "running", StringComparison.OrdinalIgnoreCase))
.Take(maxPerSection)
.ToList();
var queuedItems = visibleItems
.Where(item => string.Equals(item.State, "queued", StringComparison.OrdinalIgnoreCase) && !IsDraftBlocked(item))
.Take(maxPerSection)
.ToList();
var blockedItems = visibleItems
.Where(IsDraftBlocked)
.Take(maxPerSection)
.ToList();
var completedItems = visibleItems
.Where(item => string.Equals(item.State, "completed", StringComparison.OrdinalIgnoreCase))
.Take(maxPerSection)
.ToList();
var failedItems = visibleItems
.Where(item => string.Equals(item.State, "failed", StringComparison.OrdinalIgnoreCase))
.Take(maxPerSection)
.ToList();
if (activeItems.Count > 0)
{
DraftQueuePanel.Children.Add(CreateDraftQueueSectionLabel($"실행 대기 · {activeItems.Count}"));
foreach (var item in activeItems)
DraftQueuePanel.Children.Add(CreateDraftQueueCard(item));
}
if (historyItems.Count > 0)
{
DraftQueuePanel.Children.Add(CreateDraftQueueSectionLabel($"최근 결과 · {historyItems.Count}"));
foreach (var item in historyItems)
DraftQueuePanel.Children.Add(CreateDraftQueueCard(item));
}
if (visibleItems.Count > activeItems.Count + historyItems.Count)
{
DraftQueuePanel.Children.Add(new TextBlock
{
Text = $"추가 항목 {visibleItems.Count - (activeItems.Count + historyItems.Count)}개",
Margin = new Thickness(8, 4, 0, 0),
FontSize = 11,
Foreground = TryFindResource("SecondaryText") as Brush ?? BrushFromHex("#7A7F87"),
});
}
AddDraftQueueSection("실행 중", runningItems, summary.RunningCount);
AddDraftQueueSection("다음 작업", queuedItems, summary.QueuedCount);
AddDraftQueueSection("보류", blockedItems, summary.BlockedCount);
AddDraftQueueSection("완료", completedItems, summary.CompletedCount);
AddDraftQueueSection("실패", failedItems, summary.FailedCount);
if (summary.CompletedCount > 0 || summary.FailedCount > 0)
{
@@ -17286,6 +17302,27 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
}
}
private void AddDraftQueueSection(string label, IReadOnlyList<DraftQueueItem> items, int totalCount)
{
if (DraftQueuePanel == null || totalCount <= 0)
return;
DraftQueuePanel.Children.Add(CreateDraftQueueSectionLabel($"{label} · {totalCount}"));
foreach (var item in items)
DraftQueuePanel.Children.Add(CreateDraftQueueCard(item));
if (totalCount > items.Count)
{
DraftQueuePanel.Children.Add(new TextBlock
{
Text = $"추가 항목 {totalCount - items.Count}개",
Margin = new Thickness(8, -2, 0, 8),
FontSize = 10.5,
Foreground = TryFindResource("SecondaryText") as Brush ?? BrushFromHex("#7A7F87"),
});
}
}
private UIElement CreateDraftQueueSummaryStrip(AppStateService.DraftQueueSummaryState summary)
{
var wrap = new WrapPanel