From a35c47ed327d9479498e3694f6e6a4e811236c19 Mon Sep 17 00:00:00 2001 From: lacvet Date: Tue, 7 Apr 2026 06:40:18 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EB=AA=A8=EB=A6=AC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EA=B7=BC=EA=B1=B0=20=ED=91=9C=EC=8B=9C=EC=99=80=20?= =?UTF-8?q?=EA=B0=90=EC=82=AC/=EB=AA=85=EB=A0=B9=20UX=20=EB=A7=88=EB=AC=B4?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cowork와 Code 진행 표시 줄에 메모리 규칙 및 학습 메모리 적용 근거를 함께 노출 - include 감사 로그 최근 3일 필터와 보관 시점 판정을 정리해 메모리 감사 상태를 더 정확히 표시 - /memory list 및 search 출력 형식을 우선순위·레이어·설명·paths·tags 중심으로 재구성하고 Release 빌드 경고/오류 0 검증 --- README.md | 3 ++ docs/DEVELOPMENT.md | 14 ++++++++ src/AxCopilot/Services/Agent/MemoryTool.cs | 33 +++++++++++-------- src/AxCopilot/Services/AuditLogService.cs | 20 ++++++++++- .../Views/ChatWindow.AgentEventRendering.cs | 9 +++++ .../Views/ChatWindow.FooterPresentation.cs | 21 ++++++++++++ src/AxCopilot/Views/SettingsWindow.xaml.cs | 8 ++--- 7 files changed, 88 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 25de999..227286d 100644 --- a/README.md +++ b/README.md @@ -1423,3 +1423,6 @@ MIT License - 업데이트: 2026-04-07 01:26 (KST) - Cowork/Code 하단 메모리 칩을 눌렀을 때 `적용 중 규칙`과 `최근 include 감사`를 바로 확인할 수 있는 상세 팝업을 추가했습니다. 이제 메모리 계층이 실제로 어떻게 적용되고 있는지 채팅 하단에서 바로 추적할 수 있습니다. - 설정의 메모리 개요에도 `최근 include 감사` 요약을 추가해, 메모리 규칙 상태와 include 시도 결과를 같은 화면에서 함께 점검할 수 있게 했습니다. +- 업데이트: 2026-04-07 01:35 (KST) + - Cowork/Code 진행 표시 줄에도 `메모리 규칙 n개 · 학습 n개 적용 중` 근거가 함께 표시되도록 보강했습니다. 기다리는 동안 현재 어떤 메모리 계층이 반영되고 있는지 transcript에서 바로 확인할 수 있습니다. + - 메모리 include 감사는 `최근 3일` 기준으로 다시 집계해 보여주도록 정리했고, `/memory list`·`/memory search` 결과도 우선순위·레이어·설명·paths·tags를 두 줄 구조로 더 읽기 쉽게 정리했습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 2f20a03..5c0dfc7 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5243,3 +5243,17 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - 메모리 개요 row에 `TxtMemoryOverviewAudit` 영역을 추가해 최근 include 감사 상태를 설정 화면에서도 바로 볼 수 있게 했다. - [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) - `RefreshMemoryOverview()`가 최근 include 감사 3건을 읽어 `허용/차단 · 시간 · 결과` 형식으로 요약해 표시하도록 확장했다. + +## 2026-04-07 01:35 (KST) + +- [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs) + - Cowork/Code 진행 표시 줄 아래에 `메모리 규칙 n개 · 학습 n개 적용 중` 보조 설명을 추가했다. + - 오래 걸리는 처리 중에도 현재 어떤 메모리 계층이 반영되는지 transcript에서 바로 알 수 있도록 정리했다. +- [ChatWindow.FooterPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs) + - 진행 표시용 메모리 근거 문자열 생성 helper를 추가해 footer 상태와 transcript 근거가 같은 데이터에 기반하도록 맞췄다. +- [AuditLogService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AuditLogService.cs) + - `LoadRecent(action, maxCount, daysBack)`를 추가해 최근 N일 감사 로그를 액션별로 쉽게 읽을 수 있게 했다. + - 정리 시점 판정도 `CreationTime` 대신 `LastWriteTime`을 사용하도록 바꿔 보관 정책 오차를 줄였다. +- [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs) + - `/memory list`와 `/memory search`의 계층형 메모리 출력 형식을 두 줄 구조로 정리했다. + - 이제 각 규칙은 경로와 함께 `우선순위 · layer · description · paths · tags`를 한 번에 읽을 수 있다. diff --git a/src/AxCopilot/Services/Agent/MemoryTool.cs b/src/AxCopilot/Services/Agent/MemoryTool.cs index 74556d4..0ef9dbe 100644 --- a/src/AxCopilot/Services/Agent/MemoryTool.cs +++ b/src/AxCopilot/Services/Agent/MemoryTool.cs @@ -106,13 +106,7 @@ public class MemoryTool : IAgentTool { sb.AppendLine($"계층형 메모리 {docs.Count}개:"); foreach (var doc in docs) - { - var priority = doc.Priority > 0 ? $" (우선순위 {doc.Priority})" : ""; - var suffix = string.IsNullOrWhiteSpace(doc.Description) ? "" : $" — {doc.Description}"; - var scopeHint = doc.Paths.Count > 0 ? $" (paths: {string.Join(", ", doc.Paths)})" : ""; - var tags = doc.Tags.Count > 0 ? $" (tags: {string.Join(", ", doc.Tags)})" : ""; - sb.AppendLine($" [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}{tags}"); - } + sb.AppendLine(FormatInstructionDocument(doc)); sb.AppendLine(); } @@ -137,13 +131,7 @@ public class MemoryTool : IAgentTool { sb.AppendLine($"계층형 메모리 파일 {docs.Count}개:"); foreach (var doc in docs) - { - var priority = doc.Priority > 0 ? $" (우선순위 {doc.Priority})" : ""; - var suffix = string.IsNullOrWhiteSpace(doc.Description) ? "" : $" — {doc.Description}"; - var scopeHint = doc.Paths.Count > 0 ? $" (paths: {string.Join(", ", doc.Paths)})" : ""; - var tags = doc.Tags.Count > 0 ? $" (tags: {string.Join(", ", doc.Tags)})" : ""; - sb.AppendLine($" • [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}{tags}"); - } + sb.AppendLine(FormatInstructionDocument(doc)); sb.AppendLine(); } @@ -217,4 +205,21 @@ public class MemoryTool : IAgentTool return ToolResult.Ok($"메모리 파일 경로: {path}\n\n{content}"); } + + private static string FormatInstructionDocument(MemoryInstructionDocument doc) + { + var meta = new List + { + doc.Priority > 0 ? $"우선순위 {doc.Priority}" : "우선순위 미정", + $"layer: {doc.Layer}" + }; + if (!string.IsNullOrWhiteSpace(doc.Description)) + meta.Add(doc.Description); + if (doc.Paths.Count > 0) + meta.Add($"paths: {string.Join(", ", doc.Paths)}"); + if (doc.Tags.Count > 0) + meta.Add($"tags: {string.Join(", ", doc.Tags)}"); + + return $" • [{doc.Label}] {doc.Path}\n {string.Join(" · ", meta)}"; + } } diff --git a/src/AxCopilot/Services/AuditLogService.cs b/src/AxCopilot/Services/AuditLogService.cs index b38c037..974a050 100644 --- a/src/AxCopilot/Services/AuditLogService.cs +++ b/src/AxCopilot/Services/AuditLogService.cs @@ -87,6 +87,24 @@ public static class AuditLogService return LoadFile(Path.Combine(AuditDir, fileName)); } + /// 최근 N일 내 특정 액션 감사 로그를 최신순으로 읽습니다. + public static List LoadRecent(string action, int maxCount = 20, int daysBack = 3) + { + var since = DateTime.Now.Date.AddDays(-Math.Max(0, daysBack - 1)); + var entries = new List(); + + for (var day = DateTime.Now.Date; day >= since; day = day.AddDays(-1)) + { + entries.AddRange(LoadDate(day) + .Where(x => string.Equals(x.Action, action, StringComparison.OrdinalIgnoreCase))); + } + + return entries + .OrderByDescending(x => x.Timestamp) + .Take(Math.Max(1, maxCount)) + .ToList(); + } + private static List LoadFile(string filePath) { var entries = new List(); @@ -112,7 +130,7 @@ public static class AuditLogService var cutoff = DateTime.Now.AddDays(-retentionDays); foreach (var f in Directory.GetFiles(AuditDir, "*.json")) { - if (File.GetCreationTime(f) < cutoff) + if (File.GetLastWriteTime(f) < cutoff) File.Delete(f); } } diff --git a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs index d76fcda..6ecf40e 100644 --- a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs +++ b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs @@ -188,6 +188,15 @@ public partial class ChatWindow stack.Children.Add(bodyBlock); } + var memoryEvidence = BuildMemoryContextEvidenceText(); + if (!string.IsNullOrWhiteSpace(memoryEvidence)) + { + var memoryBlock = CreateProcessFeedBody(memoryEvidence, secondaryText); + memoryBlock.Margin = new Thickness(28, 2, 12, 8); + memoryBlock.Opacity = 0.92; + stack.Children.Add(memoryBlock); + } + if (!string.IsNullOrWhiteSpace(evt.FilePath)) { var compactPathRow = new StackPanel diff --git a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs index 6a1d93e..b863359 100644 --- a/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs @@ -381,6 +381,27 @@ public partial class ChatWindow }; } + private string? BuildMemoryContextEvidenceText() + { + if (_activeTab == "Chat") + return null; + + var app = System.Windows.Application.Current as App; + var memory = app?.MemoryService; + if (memory == null || !_settings.Settings.Llm.EnableAgentMemory) + return null; + + memory.Load(GetCurrentWorkFolder()); + var docs = memory.InstructionDocuments; + var learned = memory.All.Count; + if (docs.Count == 0 && learned == 0) + return null; + + var labels = docs.Take(2).Select(x => x.Label).ToList(); + var labelText = labels.Count == 0 ? "" : $" · {string.Join(", ", labels)}"; + return $"메모리 규칙 {docs.Count}개 · 학습 {learned}개 적용 중{labelText}"; + } + private void UpdateSelectedPresetGuide(ChatConversation? conversation = null) { if (SelectedPresetGuide == null || SelectedPresetGuideTitle == null || SelectedPresetGuideDesc == null) diff --git a/src/AxCopilot/Views/SettingsWindow.xaml.cs b/src/AxCopilot/Views/SettingsWindow.xaml.cs index 18491bc..45bc6f7 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml.cs +++ b/src/AxCopilot/Views/SettingsWindow.xaml.cs @@ -3240,9 +3240,7 @@ public partial class SettingsWindow : Window TxtMemoryOverviewScopes.Text = string.Join("\n", lines); } - var includeEntries = AuditLogService.LoadToday() - .Where(x => string.Equals(x.Action, "MemoryInclude", StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(x => x.Timestamp) + var includeEntries = AuditLogService.LoadRecent("MemoryInclude", maxCount: 3, daysBack: 3) .Take(3) .Select(x => { @@ -3252,8 +3250,8 @@ public partial class SettingsWindow : Window .ToList(); TxtMemoryOverviewAudit.Text = includeEntries.Count == 0 - ? "최근 include 감사 기록이 없습니다." - : "최근 include 감사\n" + string.Join("\n", includeEntries); + ? "최근 3일 include 감사 기록이 없습니다." + : "최근 3일 include 감사\n" + string.Join("\n", includeEntries); } // ─── 에이전트 훅 관리 ─────────────────────────────────────────────────