설정 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

@@ -379,6 +379,11 @@ ow + toggle 시각 언어로 통일했습니다.
- DraftQueue 패널 상단에 실행 중 / 다음 / 보류 / 완료 / 실패 요약 pill을 추가하고, composer 상단의 모델/컨텍스트/프리셋 줄도 더 낮고 평평한 밀도로 정리했습니다. - DraftQueue 패널 상단에 실행 중 / 다음 / 보류 / 완료 / 실패 요약 pill을 추가하고, composer 상단의 모델/컨텍스트/프리셋 줄도 더 낮고 평평한 밀도로 정리했습니다.
- 브랜치/워크트리 패널에는 공통 요약 strip을 추가해 현재 상태를 같은 시각 언어로 보여주도록 맞췄고, 저장소 루트 `.gitignore`에는 빌드 산출물·IDE 파일·OS 잡파일·비밀정보 패턴을 추가했습니다. - 브랜치/워크트리 패널에는 공통 요약 strip을 추가해 현재 상태를 같은 시각 언어로 보여주도록 맞췄고, 저장소 루트 `.gitignore`에는 빌드 산출물·IDE 파일·OS 잡파일·비밀정보 패턴을 추가했습니다.
- 검증: `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-04 23:23 (KST)
- AX Agent는 이제 설정 서비스 변경 이벤트를 직접 구독해 메인 설정, AX Agent 설정, 저장 경로와 관계없이 테마/권한/데이터 활용/모델 라벨/composer/대기열 UI를 즉시 다시 읽어오도록 fan-out 경로를 통합했습니다.
- AX Agent 설정 저장 경로에서 표현 수준을 `rich`로 고정 덮어쓰던 처리도 제거해, 사용자가 선택한 `풍부하게 / 적절하게 / 간단하게` 값이 다른 설정 저장 흐름에서도 유지되도록 보정했습니다.
- DraftQueue 패널은 실행 중 / 다음 작업 / 보류 / 완료 / 실패를 개별 섹션으로 나눠 현재 실행 상태와 최근 결과를 더 빠르게 파악할 수 있도록 재구성했습니다.
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
--- ---

View File

@@ -97,3 +97,7 @@
- 트레이 좌클릭 기본 진입점을 AX Agent로 전환하고, 우클릭 메뉴 상단에 앱 버전 헤더를 추가해 AX Agent 중심 진입 흐름을 강화함. - 트레이 좌클릭 기본 진입점을 AX Agent로 전환하고, 우클릭 메뉴 상단에 앱 버전 헤더를 추가해 AX Agent 중심 진입 흐름을 강화함.
- 메인 설정 저장 완료 후 열린 AX Agent 창이 즉시 테마/모델/권한/하단 상태줄을 다시 읽어오도록 fan-out 경로를 추가해 설정 반영 지연을 줄임. - 메인 설정 저장 완료 후 열린 AX Agent 창이 즉시 테마/모델/권한/하단 상태줄을 다시 읽어오도록 fan-out 경로를 추가해 설정 반영 지연을 줄임.
- DraftQueue kind 분류를 message/command/steering/direct/followup 기준으로 재정리해 큐 타입과 실제 입력 성격이 더 잘 맞도록 보강함. - DraftQueue kind 분류를 message/command/steering/direct/followup 기준으로 재정리해 큐 타입과 실제 입력 성격이 더 잘 맞도록 보강함.
- 업데이트: 2026-04-04 23:23 (KST)
- AX Agent가 `SettingsService.SettingsChanged`를 직접 구독하도록 바꿔 메인 설정/AX Agent 설정 어느 경로에서 저장하더라도 테마, 모델, 권한, 데이터 활용, composer, 대기열 UI가 즉시 동일 상태를 반영하도록 fan-out을 통합함.
- AX Agent 설정 저장 경로에서 표현 수준을 무조건 `rich`로 덮어쓰던 로직을 제거해 `풍부하게 / 적절하게 / 간단하게`가 다른 설정 저장 경로에서도 유지되도록 보정함.
- DraftQueue 패널은 `실행 중 / 다음 작업 / 보류 / 완료 / 실패` 개별 섹션 구조로 다시 나눠 현재 실행 흐름과 재시도 대기, 결과 이력을 더 빠르게 파악할 수 있도록 정리함.

View File

@@ -4068,3 +4068,11 @@ ow + toggle 시각 언어로 다시 정렬했다.
- 메인 설정 저장 완료 시 열려 있는 AX Agent 창이 즉시 테마, 모델, 권한, 데이터 활용, overlay/inline 빠른 설정, 하단 composer 라벨을 다시 읽어오도록 `RefreshFromSavedSettings()` fan-out 경로를 추가했다. - 메인 설정 저장 완료 시 열려 있는 AX Agent 창이 즉시 테마, 모델, 권한, 데이터 활용, overlay/inline 빠른 설정, 하단 composer 라벨을 다시 읽어오도록 `RefreshFromSavedSettings()` fan-out 경로를 추가했다.
- DraftQueue kind 판정은 일반 입력(message), 슬래시 명령(command), 조정 입력(steering), 직접 실행 요청(direct), 후속 작업(followup)이 실제 입력 형태와 더 일치하도록 보강했고, 전송 버튼은 일반 메시지 전송 경로를 사용하도록 정리했다. - DraftQueue kind 판정은 일반 입력(message), 슬래시 명령(command), 조정 입력(steering), 직접 실행 요청(direct), 후속 작업(followup)이 실제 입력 형태와 더 일치하도록 보강했고, 전송 버튼은 일반 메시지 전송 경로를 사용하도록 정리했다.
- 검증: 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-04 추가 진행 기록 (연속 실행 76차: 설정 이벤트 fan-out 통합과 큐 상태 섹션 재구성)
- 업데이트: 2026-04-04 23:23 (KST)
- AX Agent 창이 `SettingsService.SettingsChanged`를 직접 구독하도록 연결해 메인 설정, AX Agent 설정, 저장 방식이 달라도 테마/권한/데이터 활용/모델 라벨/하단 composer/대기열 UI가 즉시 같은 상태를 반영하도록 fan-out 경로를 통합했다.
- `RefreshFromSavedSettings()`는 기존 시각 요소 갱신 외에도 대기열 패널과 대화 목록까지 다시 읽어오도록 확장해 저장 후 AX Agent 화면과 실제 실행 상태가 더 빠르게 맞춰지도록 보강했다.
- AX Agent 설정 저장 경로와 구형 Agent 설정창에서 표현 수준을 무조건 `rich`로 덮어쓰던 코드를 제거해, 사용자가 선택한 `풍부하게 / 적절하게 / 간단하게` 값이 다른 설정 흐름에서도 유지되도록 수정했다.
- DraftQueue 패널은 기존 `실행 대기 / 최근 결과` 묶음에서 `실행 중 / 다음 작업 / 보류 / 완료 / 실패` 개별 섹션 구조로 재편해 queue state가 많은 경우에도 현재 진행, 다음 실행, 재시도 대기, 완료/실패 이력을 더 빠르게 구분할 수 있게 했다.
- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\ (경고 0 / 오류 0)

View File

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

View File

@@ -94,6 +94,7 @@ public partial class ChatWindow : Window
private string _gitBranchSearchText = ""; private string _gitBranchSearchText = "";
private StackPanel? _selectedMessageActionBar; private StackPanel? _selectedMessageActionBar;
private Border? _selectedMessageBorder; private Border? _selectedMessageBorder;
private bool _isRefreshingFromSettings;
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)
@@ -134,6 +135,7 @@ public partial class ChatWindow : Window
{ {
InitializeComponent(); InitializeComponent();
_settings = settings; _settings = settings;
_settings.SettingsChanged += Settings_SettingsChanged;
_storage = new ChatStorageService(); _storage = new ChatStorageService();
_llm = new LlmService(settings); _llm = new LlmService(settings);
_router = new ModelRouterService(settings); _router = new ModelRouterService(settings);
@@ -297,6 +299,7 @@ public partial class ChatWindow : Window
}; };
Closed += (_, _) => Closed += (_, _) =>
{ {
_settings.SettingsChanged -= Settings_SettingsChanged;
SubAgentTool.StatusChanged -= OnSubAgentStatusChanged; SubAgentTool.StatusChanged -= OnSubAgentStatusChanged;
_streamCts?.Cancel(); _streamCts?.Cancel();
_cursorTimer.Stop(); _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) private bool IsPermissionAutoApprovedForSession(string toolName, string target)
{ {
if (string.IsNullOrWhiteSpace(toolName) || string.IsNullOrWhiteSpace(target)) if (string.IsNullOrWhiteSpace(toolName) || string.IsNullOrWhiteSpace(target))
@@ -13956,6 +13981,8 @@ public partial class ChatWindow : Window
RefreshContextUsageVisual(); RefreshContextUsageVisual();
UpdateTabUI(); UpdateTabUI();
BuildBottomBar(); BuildBottomBar();
RefreshDraftQueueUi();
RefreshConversationList();
}, DispatcherPriority.Input); }, DispatcherPriority.Input);
} }
@@ -13978,7 +14005,6 @@ public partial class ChatWindow : Window
llm.Code.EnableCodeVerification = ChkOverlayEnableCodeVerification?.IsChecked == true; llm.Code.EnableCodeVerification = ChkOverlayEnableCodeVerification?.IsChecked == true;
llm.EnableParallelTools = ChkOverlayEnableParallelTools?.IsChecked == true; llm.EnableParallelTools = ChkOverlayEnableParallelTools?.IsChecked == true;
llm.FolderDataUsage = _folderDataUsage; llm.FolderDataUsage = _folderDataUsage;
llm.AgentUiExpressionLevel = "rich";
CommitOverlayEndpointInput(normalizeOnInvalid: true); CommitOverlayEndpointInput(normalizeOnInvalid: true);
CommitOverlayApiKeyInput(); CommitOverlayApiKeyInput();
@@ -17228,45 +17254,35 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
} }
DraftQueuePanel.Visibility = Visibility.Visible; DraftQueuePanel.Visibility = Visibility.Visible;
const int maxVisible = 8;
var summary = _appState.GetDraftQueueSummary(_activeTab); 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)); 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) AddDraftQueueSection("실행 중", runningItems, summary.RunningCount);
{ AddDraftQueueSection("다음 작업", queuedItems, summary.QueuedCount);
DraftQueuePanel.Children.Add(CreateDraftQueueSectionLabel($"실행 대기 · {activeItems.Count}")); AddDraftQueueSection("보류", blockedItems, summary.BlockedCount);
foreach (var item in activeItems) AddDraftQueueSection("완료", completedItems, summary.CompletedCount);
DraftQueuePanel.Children.Add(CreateDraftQueueCard(item)); AddDraftQueueSection("실패", failedItems, summary.FailedCount);
}
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"),
});
}
if (summary.CompletedCount > 0 || summary.FailedCount > 0) 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) private UIElement CreateDraftQueueSummaryStrip(AppStateService.DraftQueueSummaryState summary)
{ {
var wrap = new WrapPanel var wrap = new WrapPanel