AX Agent 메모리 편집 UI 추가 및 계층형 메모리 관리 흐름 보강
Some checks failed
Release Gate / gate (push) Has been cancelled

- AX Agent 설정의 에이전트 메모리 섹션에 관리형/사용자/프로젝트/로컬 메모리 편집 버튼 추가
- 계층형 메모리 파일을 현재 테마 기반 다이얼로그에서 직접 열고 저장/삭제할 수 있는 편집 UI 구현
- description 및 paths frontmatter 예시를 편집기 안내에 포함해 claw-code 수준의 메모리 규칙 작성 흐름 보강
- 저장 후 메모리 계층을 즉시 다시 로드하도록 연결해 /memory 명령과 설정 UI가 같은 상태를 보도록 정리
- README 및 DEVELOPMENT 문서에 2026-04-07 00:45 (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-07 00:28:56 +09:00
parent 917e61af20
commit 4e1dcf082c
4 changed files with 267 additions and 9 deletions

View File

@@ -1408,3 +1408,6 @@ MIT License
- 업데이트: 2026-04-07 00:39 (KST)
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)에 계층형 메모리 우선순위/병합 정책을 추가했습니다. 같은 내용의 규칙이 여러 계층에 중복될 경우 더 가까운 규칙만 남기고, 최종 메모리 문서는 `managed → user → project → local` 순으로 다시 정렬됩니다.
- [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs) 의 `list/search`는 이제 최종 우선순위 번호를 같이 보여줘, 어떤 규칙이 실제로 더 강하게 적용되는지 바로 확인할 수 있습니다.
- 업데이트: 2026-04-07 00:45 (KST)
- AX Copilot 메인 설정의 에이전트 메모리 영역에서 `관리형 / 사용자 / 프로젝트 / 로컬` 메모리 파일을 직접 열어 수정할 수 있게 했습니다. [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml), [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs)에 계층형 메모리 편집 버튼과 전용 편집 다이얼로그를 추가했습니다.
- 메모리 편집 다이얼로그는 현재 테마를 따르는 안내 패널과 멀티라인 편집기를 제공하고, `description`/`paths` frontmatter 예시를 바로 볼 수 있습니다. 저장 시 해당 scope 파일을 즉시 갱신하고, 빈 내용으로 저장하면 파일을 삭제한 뒤 메모리 계층을 다시 로드합니다.

View File

@@ -5195,3 +5195,13 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs)
- `list`, `search` 출력에 계층형 메모리 문서의 우선순위를 함께 노출하도록 정리했다.
- 사용자는 이제 `/memory` 결과에서 어떤 규칙이 실제로 더 강하게 적용되는지 바로 읽을 수 있다.
## 2026-04-07 00:45 (KST)
- [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml)
- 에이전트 메모리 섹션에 `관리형 편집`, `사용자 편집`, `프로젝트 편집`, `로컬 편집` 버튼을 추가했다.
- 메모리 관리는 `/memory` 명령만이 아니라 설정 UI에서도 직접 접근 가능하도록 확장했고, 같은 섹션 안에서 학습 메모리 초기화와 계층형 메모리 편집을 함께 다룰 수 있게 정리했다.
- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs)
- scope별 메모리 파일 경로를 계산해 실제 내용을 읽고 수정할 수 있는 `OpenMemoryScopeEditor(...)`를 추가했다.
- 편집 다이얼로그는 현재 테마 색을 사용하고, `description`/`paths` frontmatter 예시를 안내로 보여준다.
- 저장 시 해당 scope 메모리 파일을 즉시 갱신하고, 빈 내용이면 파일을 삭제한 뒤 `AgentMemoryService.Load(...)`를 다시 호출해 메모리 계층이 곧바로 반영되도록 했다.

View File

@@ -114,6 +114,36 @@
<Setter Property="Margin" Value="2,18,0,8"/>
</Style>
<Style x:Key="MemoryScopeButton" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource ItemBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource PrimaryText}"/>
<Setter Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="12,6"/>
<Setter Property="Margin" Value="0,0,8,8"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource AccentColor}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ─── 도움말 아이콘 (? 버튼 + 커스텀 툴팁) ──────────────────────── -->
<Style x:Key="HelpTooltipStyle" TargetType="ToolTip">
@@ -4860,15 +4890,21 @@
</StackPanel>
</Grid>
</Border>
<Border Style="{StaticResource AgentSettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="메모리 관리"/>
<TextBlock Style="{StaticResource RowHint}" Text="현재 작업 폴더의 메모리를 확인하거나 초기화합니다."/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Content="메모리 초기화" Click="BtnClearMemory_Click"
Background="#DC2626" Foreground="White" FontSize="12"
<Border Style="{StaticResource AgentSettingsRow}">
<Grid>
<StackPanel HorizontalAlignment="Left">
<TextBlock Style="{StaticResource RowLabel}" Text="메모리 관리"/>
<TextBlock Style="{StaticResource RowHint}" Text="계층형 메모리 파일을 직접 열어 수정하거나, 학습 메모리를 초기화합니다."/>
<WrapPanel Margin="0,10,0,0">
<Button Style="{StaticResource MemoryScopeButton}" Content="관리형 편집" Click="BtnEditManagedMemory_Click"/>
<Button Style="{StaticResource MemoryScopeButton}" Content="사용자 편집" Click="BtnEditUserMemory_Click"/>
<Button Style="{StaticResource MemoryScopeButton}" Content="프로젝트 편집" Click="BtnEditProjectMemory_Click"/>
<Button Style="{StaticResource MemoryScopeButton}" Content="로컬 편집" Click="BtnEditLocalMemory_Click"/>
</WrapPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Content="메모리 초기화" Click="BtnClearMemory_Click"
Background="#DC2626" Foreground="White" FontSize="12"
Padding="14,6" Cursor="Hand" BorderThickness="0" Margin="8,0,0,0">
<Button.Template>
<ControlTemplate TargetType="Button">

View File

@@ -1,4 +1,5 @@
using System.Linq;
using System.IO;
using System.Windows.Data;
using System.Windows;
using System.Windows.Controls;
@@ -2981,6 +2982,214 @@ public partial class SettingsWindow : Window
CustomMessageBox.Show("에이전트 메모리가 초기화되었습니다.", "완료", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void BtnEditManagedMemory_Click(object sender, RoutedEventArgs e) => OpenMemoryScopeEditor("managed", "관리형 메모리");
private void BtnEditUserMemory_Click(object sender, RoutedEventArgs e) => OpenMemoryScopeEditor("user", "사용자 메모리");
private void BtnEditProjectMemory_Click(object sender, RoutedEventArgs e) => OpenMemoryScopeEditor("project", "프로젝트 메모리");
private void BtnEditLocalMemory_Click(object sender, RoutedEventArgs e) => OpenMemoryScopeEditor("local", "로컬 메모리");
private void OpenMemoryScopeEditor(string scope, string title)
{
var app = System.Windows.Application.Current as App;
var memory = app?.MemoryService;
if (memory == null)
{
CustomMessageBox.Show("메모리 서비스를 사용할 수 없습니다.", "오류", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
var workFolder = _vm.Service.Settings.Llm.WorkFolder;
var path = memory.GetWritableInstructionPath(scope, workFolder);
if (string.IsNullOrWhiteSpace(path))
{
CustomMessageBox.Show("해당 메모리 파일 경로를 결정할 수 없습니다. 작업 폴더를 먼저 확인하세요.", "메모리 편집", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var existing = memory.ReadInstructionFile(scope, workFolder) ?? "";
var bgBrush = TryFindResource("LauncherBackground") as Brush ?? Brushes.White;
var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.LightGray;
var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.WhiteSmoke;
var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Gainsboro;
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
var dlg = new Window
{
Title = title,
Width = 760,
Height = 620,
MinWidth = 620,
MinHeight = 480,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = this,
Background = bgBrush,
Foreground = fgBrush,
ShowInTaskbar = false
};
var root = new Grid { Margin = new Thickness(18) };
root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
var header = new StackPanel { Margin = new Thickness(0, 0, 0, 12) };
header.Children.Add(new TextBlock
{
Text = title,
FontSize = 18,
FontWeight = FontWeights.SemiBold,
Foreground = fgBrush
});
header.Children.Add(new TextBlock
{
Text = $"경로: {path}",
FontSize = 12,
Foreground = subFgBrush,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 6, 0, 0)
});
Grid.SetRow(header, 0);
root.Children.Add(header);
var hint = new Border
{
Background = itemBg,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(10),
Padding = new Thickness(12, 10, 12, 10),
Margin = new Thickness(0, 0, 0, 12),
Child = new TextBlock
{
Text = "frontmatter 예시:\n---\ndescription: 결제 모듈 규칙\npaths:\n - src/Payments/**\n---\n\n메모리 내용은 /memory 명령과 함께 사용되며, 빈 내용으로 저장하면 파일이 삭제됩니다.",
FontSize = 12,
Foreground = subFgBrush,
TextWrapping = TextWrapping.Wrap,
LineHeight = 18
}
};
Grid.SetRow(hint, 1);
root.Children.Add(hint);
var editor = new TextBox
{
Text = existing,
AcceptsReturn = true,
AcceptsTab = true,
TextWrapping = TextWrapping.Wrap,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
Background = Brushes.White,
Foreground = fgBrush,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(14),
FontSize = 13,
FontFamily = new FontFamily("Consolas, Malgun Gothic, Segoe UI")
};
Grid.SetRow(editor, 2);
root.Children.Add(editor);
var footer = new Grid { Margin = new Thickness(0, 14, 0, 0) };
footer.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
footer.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
footer.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
var openFolder = new Border
{
Background = itemBg,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(12, 7, 12, 7),
Cursor = Cursors.Hand,
Child = new TextBlock { Text = "폴더 열기", FontSize = 12, Foreground = fgBrush }
};
openFolder.MouseEnter += (_, _) => openFolder.Background = hoverBg;
openFolder.MouseLeave += (_, _) => openFolder.Background = itemBg;
openFolder.MouseLeftButtonUp += (_, _) =>
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{path}\"",
UseShellExecute = true
});
}
catch (Exception ex)
{
CustomMessageBox.Show($"폴더를 열 수 없습니다.\n{ex.Message}", "메모리 편집", MessageBoxButton.OK, MessageBoxImage.Warning);
}
};
Grid.SetColumn(openFolder, 0);
footer.Children.Add(openFolder);
var cancelBtn = new Border
{
Background = itemBg,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8),
Padding = new Thickness(16, 7, 16, 7),
Cursor = Cursors.Hand,
Margin = new Thickness(8, 0, 0, 0),
Child = new TextBlock { Text = "취소", FontSize = 12, Foreground = fgBrush }
};
cancelBtn.MouseEnter += (_, _) => cancelBtn.Background = hoverBg;
cancelBtn.MouseLeave += (_, _) => cancelBtn.Background = itemBg;
cancelBtn.MouseLeftButtonUp += (_, _) => dlg.Close();
Grid.SetColumn(cancelBtn, 1);
footer.Children.Add(cancelBtn);
var saveBtn = new Border
{
Background = accentBrush,
CornerRadius = new CornerRadius(8),
Padding = new Thickness(16, 7, 16, 7),
Cursor = Cursors.Hand,
Margin = new Thickness(8, 0, 0, 0),
Child = new TextBlock { Text = "저장", FontSize = 12, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White }
};
saveBtn.MouseLeftButtonUp += (_, _) =>
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var text = editor.Text.Trim();
if (string.IsNullOrWhiteSpace(text))
{
if (File.Exists(path))
File.Delete(path);
}
else
{
File.WriteAllText(path, editor.Text.Replace("\r\n", "\n"));
}
memory.Load(workFolder);
CustomMessageBox.Show("메모리 파일이 저장되었습니다.", "메모리 편집", MessageBoxButton.OK, MessageBoxImage.Information);
dlg.Close();
}
catch (Exception ex)
{
CustomMessageBox.Show($"메모리 파일 저장에 실패했습니다.\n{ex.Message}", "메모리 편집", MessageBoxButton.OK, MessageBoxImage.Error);
}
};
Grid.SetColumn(saveBtn, 2);
footer.Children.Add(saveBtn);
Grid.SetRow(footer, 3);
root.Children.Add(footer);
dlg.Content = root;
dlg.ShowDialog();
}
// ─── 에이전트 훅 관리 ─────────────────────────────────────────────────
private void AddHookBtn_Click(object sender, MouseButtonEventArgs e)