diff --git a/README.md b/README.md index 3a6cc18..5206fbe 100644 --- a/README.md +++ b/README.md @@ -1417,3 +1417,6 @@ MIT License - 업데이트: 2026-04-07 01:00 (KST) - AX Agent 설정의 에이전트 메모리 섹션에 `적용 중 메모리 계층` 요약을 추가했습니다. 현재 컨텍스트에 반영된 계층형 규칙 수와 학습 메모리 수를 한 줄로 보고, 아래에서 활성 규칙 파일의 우선순위·설명·태그를 바로 확인할 수 있습니다. - 메모리 파일을 편집하거나 학습 메모리를 초기화하면 이 요약이 즉시 다시 계산되도록 연결했고, `새로고침` 버튼도 추가해 현재 작업 폴더 기준 메모리 적용 상태를 바로 다시 확인할 수 있게 했습니다. +업데이트: 2026-04-07 01:15 (KST) + +- AX Agent 메모리 구조를 추가 강화했습니다. `@include` 확장 시도는 이제 감사 로그에 `MemoryInclude` 항목으로 남고, Cowork/Code 하단 폴더 바에 현재 적용 중인 계층형 메모리/학습 메모리 상태가 요약 표시됩니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index dd7d11b..e555e22 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5226,3 +5226,9 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) - `RefreshMemoryOverview()`를 추가해 현재 작업 폴더 기준 메모리 계층 적용 상태를 계산해 UI에 표시하도록 했다. - 설정 창 로드 시, 메모리 파일 저장 후, 학습 메모리 초기화 후 모두 이 요약을 자동으로 갱신해 설정 UI와 실제 적용 상태가 어긋나지 않도록 정리했다. +업데이트: 2026-04-07 01:15 (KST) + +- 메모리 구조 후속 고도화: + - `AgentMemoryService`에서 `@include` 시도 성공/차단을 감사 로그(`MemoryInclude`)로 기록 + - `ChatWindow` 하단 폴더 바에 메모리 상태 요약(`메모리 n · 학습 n`) 추가 + - 설정의 `외부 메모리 include 허용` 안내 문구를 감사 로그 기준으로 갱신 diff --git a/src/AxCopilot/Services/AgentMemoryService.cs b/src/AxCopilot/Services/AgentMemoryService.cs index 38af009..9e84634 100644 --- a/src/AxCopilot/Services/AgentMemoryService.cs +++ b/src/AxCopilot/Services/AgentMemoryService.cs @@ -570,10 +570,11 @@ public class AgentMemoryService if (!inCodeBlock && trimmed.StartsWith("@", StringComparison.Ordinal) && trimmed.Length > 1) { - var includePath = ResolveIncludePath(fullPath, trimmed, projectRoot, IsExternalMemoryIncludeAllowed()); - if (!string.IsNullOrWhiteSpace(includePath)) + var resolution = ResolveIncludePath(fullPath, trimmed, projectRoot, IsExternalMemoryIncludeAllowed()); + LogIncludeAudit(fullPath, trimmed, resolution); + if (!string.IsNullOrWhiteSpace(resolution.ResolvedPath)) { - var included = ExpandInstructionIncludes(includePath, projectRoot, visited, depth + 1); + var included = ExpandInstructionIncludes(resolution.ResolvedPath, projectRoot, visited, depth + 1); if (!string.IsNullOrWhiteSpace(included)) { sb.AppendLine(included.TrimEnd()); @@ -595,18 +596,18 @@ public class AgentMemoryService } } - private static string? ResolveIncludePath(string currentFile, string includeDirective, string? projectRoot, bool allowExternal) + private static MemoryIncludeResolution ResolveIncludePath(string currentFile, string includeDirective, string? projectRoot, bool allowExternal) { var target = includeDirective[1..].Trim(); if (string.IsNullOrWhiteSpace(target)) - return null; + return MemoryIncludeResolution.CreateFailure("", "빈 include 지시문"); var hashIndex = target.IndexOf('#'); if (hashIndex >= 0) target = target[..hashIndex]; if (string.IsNullOrWhiteSpace(target)) - return null; + return MemoryIncludeResolution.CreateFailure("", "주석만 포함된 include 지시문"); string resolved; var externalCandidate = false; @@ -634,18 +635,20 @@ public class AgentMemoryService if (!string.IsNullOrWhiteSpace(ext) && !new[] { ".md", ".txt", ".cs", ".json", ".yml", ".yaml", ".xml", ".props", ".targets" } .Contains(ext, StringComparer.OrdinalIgnoreCase)) - return null; + return MemoryIncludeResolution.CreateFailure(resolved, $"지원하지 않는 확장자 {ext}"); if (!allowExternal) { if (externalCandidate) - return null; + return MemoryIncludeResolution.CreateFailure(resolved, "외부 include가 비활성화되어 있습니다"); if (!string.IsNullOrWhiteSpace(projectRoot) && !IsSubPathOf(projectRoot, resolved)) - return null; + return MemoryIncludeResolution.CreateFailure(resolved, "프로젝트 바깥 경로는 허용되지 않습니다"); } - return File.Exists(resolved) ? resolved : null; + return File.Exists(resolved) + ? MemoryIncludeResolution.CreateSuccess(resolved) + : MemoryIncludeResolution.CreateFailure(resolved, "포함할 파일을 찾지 못했습니다"); } private static bool IsSubPathOf(string baseDirectory, string candidatePath) @@ -680,6 +683,34 @@ public class AgentMemoryService } } + private static void LogIncludeAudit(string sourcePath, string directive, MemoryIncludeResolution resolution) + { + try + { + var app = System.Windows.Application.Current as App; + if (app?.SettingsService?.Settings.Llm.EnableAuditLog != true) + return; + + AuditLogService.Log(new AuditEntry + { + ConversationId = "", + Tab = "Memory", + Action = "MemoryInclude", + ToolName = "memory", + Parameters = $"{sourcePath} <= {directive}", + Result = resolution.Success + ? $"허용: {resolution.ResolvedPath}" + : $"차단: {resolution.Reason}", + FilePath = resolution.ResolvedPath, + Success = resolution.Success, + }); + } + catch + { + // 감사 로그 실패는 메모리 로드에 영향 주지 않음 + } + } + public string? ReadInstructionFile(string scope, string? workFolder) { lock (_lock) @@ -913,3 +944,24 @@ public class MemoryInstructionDocument [JsonPropertyName("priority")] public int Priority { get; set; } } + +internal sealed class MemoryIncludeResolution +{ + public string? ResolvedPath { get; init; } + public string Reason { get; init; } = ""; + public bool Success { get; init; } + + public static MemoryIncludeResolution CreateSuccess(string path) => new() + { + ResolvedPath = path, + Success = true, + Reason = "허용" + }; + + public static MemoryIncludeResolution CreateFailure(string? path, string reason) => new() + { + ResolvedPath = string.IsNullOrWhiteSpace(path) ? null : path, + Success = false, + Reason = reason + }; +} diff --git a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs index d3989f7..9777449 100644 --- a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs @@ -76,6 +76,7 @@ public partial class ChatWindow LoadCompactionMetricsFromConversation(); UpdatePermissionUI(); UpdateDataUsageUI(); + UpdateMemoryStatusUi(); RefreshContextUsageVisual(); ScheduleGitBranchRefresh(); UpdateGitBranchUi(_currentGitBranchName, GitBranchFilesText?.Text ?? "", GitBranchAddedText?.Text ?? "", GitBranchDeletedText?.Text ?? "", _currentGitTooltip ?? "", BtnGitBranch?.Visibility ?? Visibility.Collapsed); @@ -86,6 +87,61 @@ public partial class ChatWindow _folderDataUsage = GetAutomaticFolderDataUsage(); } + private void UpdateMemoryStatusUi() + { + if (BtnMemoryStatus == null || MemoryStatusLabel == null) + return; + + if (_activeTab == "Chat") + { + BtnMemoryStatus.Visibility = Visibility.Collapsed; + MemoryStatusSeparator.Visibility = Visibility.Collapsed; + return; + } + + var app = System.Windows.Application.Current as App; + var memory = app?.MemoryService; + if (memory == null || !_settings.Settings.Llm.EnableAgentMemory) + { + MemoryStatusLabel.Text = "메모리 꺼짐"; + BtnMemoryStatus.ToolTip = "에이전트 메모리가 비활성화되어 있습니다."; + BtnMemoryStatus.Visibility = Visibility.Visible; + MemoryStatusSeparator.Visibility = Visibility.Visible; + return; + } + + var workFolder = GetCurrentWorkFolder(); + memory.Load(workFolder); + var docs = memory.InstructionDocuments; + var learned = memory.All.Count; + + MemoryStatusLabel.Text = docs.Count > 0 || learned > 0 + ? $"메모리 {docs.Count} · 학습 {learned}" + : "메모리 없음"; + + var lines = docs + .Take(4) + .Select(doc => + { + var priority = doc.Priority > 0 ? $"우선순위 {doc.Priority}" : "우선순위 미정"; + var description = string.IsNullOrWhiteSpace(doc.Description) ? "" : $" · {doc.Description}"; + return $"[{doc.Label}] {priority}{description}"; + }) + .ToList(); + + if (docs.Count > lines.Count) + lines.Add($"외 {docs.Count - lines.Count}개 규칙"); + + var includePolicy = _settings.Settings.Llm.AllowExternalMemoryIncludes + ? "외부 include 허용" + : "외부 include 차단"; + BtnMemoryStatus.ToolTip = lines.Count == 0 + ? $"계층형 규칙이 없습니다.\n학습 메모리 {learned}개\n{includePolicy}" + : $"계층형 규칙 {docs.Count}개 · 학습 메모리 {learned}개\n{string.Join("\n", lines)}\n{includePolicy}"; + BtnMemoryStatus.Visibility = Visibility.Visible; + MemoryStatusSeparator.Visibility = Visibility.Visible; + } + private void UpdateSelectedPresetGuide(ChatConversation? conversation = null) { if (SelectedPresetGuide == null || SelectedPresetGuideTitle == null || SelectedPresetGuideDesc == null) diff --git a/src/AxCopilot/Views/ChatWindow.xaml b/src/AxCopilot/Views/ChatWindow.xaml index aa9f15e..b989056 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml +++ b/src/AxCopilot/Views/ChatWindow.xaml @@ -2379,9 +2379,11 @@ - + - + + + @@ -2412,8 +2414,27 @@ + + + + - -