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

@@ -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)