Compare commits
2
Commits
fe843fb314
...
4e6d5d0597
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e6d5d0597 | ||
|
|
a35c47ed32 |
@@ -1423,3 +1423,8 @@ MIT License
|
|||||||
- 업데이트: 2026-04-07 01:26 (KST)
|
- 업데이트: 2026-04-07 01:26 (KST)
|
||||||
- Cowork/Code 하단 메모리 칩을 눌렀을 때 `적용 중 규칙`과 `최근 include 감사`를 바로 확인할 수 있는 상세 팝업을 추가했습니다. 이제 메모리 계층이 실제로 어떻게 적용되고 있는지 채팅 하단에서 바로 추적할 수 있습니다.
|
- Cowork/Code 하단 메모리 칩을 눌렀을 때 `적용 중 규칙`과 `최근 include 감사`를 바로 확인할 수 있는 상세 팝업을 추가했습니다. 이제 메모리 계층이 실제로 어떻게 적용되고 있는지 채팅 하단에서 바로 추적할 수 있습니다.
|
||||||
- 설정의 메모리 개요에도 `최근 include 감사` 요약을 추가해, 메모리 규칙 상태와 include 시도 결과를 같은 화면에서 함께 점검할 수 있게 했습니다.
|
- 설정의 메모리 개요에도 `최근 include 감사` 요약을 추가해, 메모리 규칙 상태와 include 시도 결과를 같은 화면에서 함께 점검할 수 있게 했습니다.
|
||||||
|
- 업데이트: 2026-04-07 01:35 (KST)
|
||||||
|
- Cowork/Code 진행 표시 줄에도 `메모리 규칙 n개 · 학습 n개 적용 중` 근거가 함께 표시되도록 보강했습니다. 기다리는 동안 현재 어떤 메모리 계층이 반영되고 있는지 transcript에서 바로 확인할 수 있습니다.
|
||||||
|
- 메모리 include 감사는 `최근 3일` 기준으로 다시 집계해 보여주도록 정리했고, `/memory list`·`/memory search` 결과도 우선순위·레이어·설명·paths·tags를 두 줄 구조로 더 읽기 쉽게 정리했습니다.
|
||||||
|
- 업데이트: 2026-04-07 01:44 (KST)
|
||||||
|
- AX Agent footer/preset 안내에 남아 있던 한글 깨짐 문자열을 복구했습니다. Cowork/Code 입력창 워터마크, 선택된 프리셋 설명, 메모리 상태 팝업 문구가 정상 한글로 다시 표시됩니다.
|
||||||
|
|||||||
@@ -5243,3 +5243,23 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
|||||||
- 메모리 개요 row에 `TxtMemoryOverviewAudit` 영역을 추가해 최근 include 감사 상태를 설정 화면에서도 바로 볼 수 있게 했다.
|
- 메모리 개요 row에 `TxtMemoryOverviewAudit` 영역을 추가해 최근 include 감사 상태를 설정 화면에서도 바로 볼 수 있게 했다.
|
||||||
- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs)
|
- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs)
|
||||||
- `RefreshMemoryOverview()`가 최근 include 감사 3건을 읽어 `허용/차단 · 시간 · 결과` 형식으로 요약해 표시하도록 확장했다.
|
- `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`를 한 번에 읽을 수 있다.
|
||||||
|
|
||||||
|
## 2026-04-07 01:44 (KST)
|
||||||
|
|
||||||
|
- [ChatWindow.FooterPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.FooterPresentation.cs)
|
||||||
|
- Cowork/Code 입력창 워터마크, 선택된 프리셋 안내, 메모리 상태 팝업 문구에 섞여 들어간 깨진 한글 리터럴을 정상 문자열로 복구했다.
|
||||||
|
- `메모리 규칙 n개 · 학습 n개 적용 중` 근거 표시와 메모리 팝업 상세 문구도 읽기 쉬운 한글 기준으로 다시 정리했다.
|
||||||
|
|||||||
@@ -106,13 +106,7 @@ public class MemoryTool : IAgentTool
|
|||||||
{
|
{
|
||||||
sb.AppendLine($"계층형 메모리 {docs.Count}개:");
|
sb.AppendLine($"계층형 메모리 {docs.Count}개:");
|
||||||
foreach (var doc in docs)
|
foreach (var doc in docs)
|
||||||
{
|
sb.AppendLine(FormatInstructionDocument(doc));
|
||||||
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();
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,13 +131,7 @@ public class MemoryTool : IAgentTool
|
|||||||
{
|
{
|
||||||
sb.AppendLine($"계층형 메모리 파일 {docs.Count}개:");
|
sb.AppendLine($"계층형 메모리 파일 {docs.Count}개:");
|
||||||
foreach (var doc in docs)
|
foreach (var doc in docs)
|
||||||
{
|
sb.AppendLine(FormatInstructionDocument(doc));
|
||||||
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();
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,4 +205,21 @@ public class MemoryTool : IAgentTool
|
|||||||
|
|
||||||
return ToolResult.Ok($"메모리 파일 경로: {path}\n\n{content}");
|
return ToolResult.Ok($"메모리 파일 경로: {path}\n\n{content}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatInstructionDocument(MemoryInstructionDocument doc)
|
||||||
|
{
|
||||||
|
var meta = new List<string>
|
||||||
|
{
|
||||||
|
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)}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,24 @@ public static class AuditLogService
|
|||||||
return LoadFile(Path.Combine(AuditDir, fileName));
|
return LoadFile(Path.Combine(AuditDir, fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>최근 N일 내 특정 액션 감사 로그를 최신순으로 읽습니다.</summary>
|
||||||
|
public static List<AuditEntry> LoadRecent(string action, int maxCount = 20, int daysBack = 3)
|
||||||
|
{
|
||||||
|
var since = DateTime.Now.Date.AddDays(-Math.Max(0, daysBack - 1));
|
||||||
|
var entries = new List<AuditEntry>();
|
||||||
|
|
||||||
|
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<AuditEntry> LoadFile(string filePath)
|
private static List<AuditEntry> LoadFile(string filePath)
|
||||||
{
|
{
|
||||||
var entries = new List<AuditEntry>();
|
var entries = new List<AuditEntry>();
|
||||||
@@ -112,7 +130,7 @@ public static class AuditLogService
|
|||||||
var cutoff = DateTime.Now.AddDays(-retentionDays);
|
var cutoff = DateTime.Now.AddDays(-retentionDays);
|
||||||
foreach (var f in Directory.GetFiles(AuditDir, "*.json"))
|
foreach (var f in Directory.GetFiles(AuditDir, "*.json"))
|
||||||
{
|
{
|
||||||
if (File.GetCreationTime(f) < cutoff)
|
if (File.GetLastWriteTime(f) < cutoff)
|
||||||
File.Delete(f);
|
File.Delete(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,15 @@ public partial class ChatWindow
|
|||||||
stack.Children.Add(bodyBlock);
|
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))
|
if (!string.IsNullOrWhiteSpace(evt.FilePath))
|
||||||
{
|
{
|
||||||
var compactPathRow = new StackPanel
|
var compactPathRow = new StackPanel
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ public partial class ChatWindow
|
|||||||
return _activeTab switch
|
return _activeTab switch
|
||||||
{
|
{
|
||||||
"Cowork" => hasFolder
|
"Cowork" => hasFolder
|
||||||
? "臾몄꽌 ?묒꽦, ?곗씠??遺꾩꽍, ?뚯씪 ?묒뾽???붿껌?섏꽭?? ?꾩슂?섎㈃ ?묒뾽 ?대뜑 ?뚯씪???④퍡 李멸퀬?⑸땲??"
|
? "문서 작성, 데이터 분석, 파일 작업을 요청하세요. 필요하면 작업 폴더 파일도 함께 참고합니다."
|
||||||
: "臾몄꽌 ?묒꽦, ?곗씠??遺꾩꽍, ?뚯씪 ?묒뾽???붿껌?섏꽭?? ?묒뾽 ?대뜑瑜??좏깮?섎㈃ 愿???뚯씪???④퍡 李멸퀬?⑸땲??",
|
: "문서 작성, 데이터 분석, 파일 작업을 요청하세요. 작업 폴더를 선택하면 관련 파일도 함께 참고합니다.",
|
||||||
"Code" => hasFolder
|
"Code" => hasFolder
|
||||||
? "肄붾뱶 ?섏젙, ?먯씤 遺꾩꽍, 鍮뚮뱶쨌?뚯뒪?몃? ?붿껌?섏꽭?? ?묒뾽 ?대뜑 肄붾뱶瑜?李멸퀬?섍퀬, ?곷떒 ??μ냼 諛곕꼫濡?釉뚮옖移섏? 蹂寃??곹깭瑜??④퍡 遊낅땲??"
|
? "코드 수정, 원인 분석, 빌드·테스트를 요청하세요. 작업 폴더 코드를 참고하고, 상단 저장소 배너로 브랜치와 변경 상태를 함께 봅니다."
|
||||||
: "?묒뾽 ?대뜑瑜??좏깮????肄붾뱶 ?섏젙, ?먯씤 遺꾩꽍, 鍮뚮뱶쨌?뚯뒪?몃? ?붿껌?섏꽭??",
|
: "작업 폴더를 선택한 뒤 코드 수정, 원인 분석, 빌드·테스트를 요청하세요.",
|
||||||
_ => "吏덈Ц, ?붿빟, 珥덉븞 ?묒꽦, ?꾩씠?붿뼱 ?뺣━瑜??붿껌?섏꽭??",
|
_ => "질문, 요약, 초안 작성, 아이디어 정리를 요청하세요.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +47,9 @@ public partial class ChatWindow
|
|||||||
return preset.Description.Trim();
|
return preset.Description.Trim();
|
||||||
|
|
||||||
if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase))
|
||||||
return "?좏깮???묒뾽 ?좏삎??留욌뒗 臾몄꽌쨌?곗씠?걔룻뙆???묒뾽 ?먮쫫?쇰줈 ?댁뼱吏묐땲??";
|
return "선택된 작업 유형에 맞는 문서·데이터·파일 작업 흐름으로 이어집니다.";
|
||||||
|
|
||||||
return "?좏깮?????二쇱젣??留욌뒗 ?묐떟 諛⑺뼢怨?珥덉븞 ?먮쫫?쇰줈 ?댁뼱吏묐땲??";
|
return "선택된 대화 주제에 맞는 응답 방향과 초안 흐름으로 이어집니다.";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFolderBar()
|
private void UpdateFolderBar()
|
||||||
@@ -74,7 +74,7 @@ public partial class ChatWindow
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FolderPathLabel.Text = "?대뜑瑜??좏깮?섏꽭??";
|
FolderPathLabel.Text = "폴더를 선택하세요";
|
||||||
FolderPathLabel.ToolTip = null;
|
FolderPathLabel.ToolTip = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,11 +168,7 @@ public partial class ChatWindow
|
|||||||
? "외부 include 허용"
|
? "외부 include 허용"
|
||||||
: "외부 include 차단";
|
: "외부 include 차단";
|
||||||
var auditEnabled = _settings.Settings.Llm.EnableAuditLog;
|
var auditEnabled = _settings.Settings.Llm.EnableAuditLog;
|
||||||
var recentIncludeEntries = AuditLogService.LoadToday()
|
var recentIncludeEntries = AuditLogService.LoadRecent("MemoryInclude", maxCount: 5, daysBack: 3);
|
||||||
.Where(x => string.Equals(x.Action, "MemoryInclude", StringComparison.OrdinalIgnoreCase))
|
|
||||||
.OrderByDescending(x => x.Timestamp)
|
|
||||||
.Take(5)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||||
@@ -251,7 +247,7 @@ public partial class ChatWindow
|
|||||||
{
|
{
|
||||||
panel.Children.Add(new TextBlock
|
panel.Children.Add(new TextBlock
|
||||||
{
|
{
|
||||||
Text = "오늘 기록된 include 시도가 없습니다.",
|
Text = "최근 3일 include 감사 기록이 없습니다.",
|
||||||
FontSize = 11,
|
FontSize = 11,
|
||||||
Foreground = secondaryText,
|
Foreground = secondaryText,
|
||||||
Margin = new Thickness(8, 0, 8, 6),
|
Margin = new Thickness(8, 0, 8, 6),
|
||||||
@@ -381,6 +377,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)
|
private void UpdateSelectedPresetGuide(ChatConversation? conversation = null)
|
||||||
{
|
{
|
||||||
if (SelectedPresetGuide == null || SelectedPresetGuideTitle == null || SelectedPresetGuideDesc == null)
|
if (SelectedPresetGuide == null || SelectedPresetGuideTitle == null || SelectedPresetGuideDesc == null)
|
||||||
@@ -415,8 +432,8 @@ public partial class ChatWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectedPresetGuideTitle.Text = string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase)
|
SelectedPresetGuideTitle.Text = string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase)
|
||||||
? $"?좏깮???묒뾽 ?좏삎 쨌 {preset.Label}"
|
? $"선택된 작업 유형 · {preset.Label}"
|
||||||
: $"?좏깮?????二쇱젣 쨌 {preset.Label}";
|
: $"선택된 대화 주제 · {preset.Label}";
|
||||||
SelectedPresetGuideDesc.Text = BuildSelectedPresetGuideDescription(preset);
|
SelectedPresetGuideDesc.Text = BuildSelectedPresetGuideDescription(preset);
|
||||||
SelectedPresetGuide.Visibility = Visibility.Visible;
|
SelectedPresetGuide.Visibility = Visibility.Visible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3240,9 +3240,7 @@ public partial class SettingsWindow : Window
|
|||||||
TxtMemoryOverviewScopes.Text = string.Join("\n", lines);
|
TxtMemoryOverviewScopes.Text = string.Join("\n", lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
var includeEntries = AuditLogService.LoadToday()
|
var includeEntries = AuditLogService.LoadRecent("MemoryInclude", maxCount: 3, daysBack: 3)
|
||||||
.Where(x => string.Equals(x.Action, "MemoryInclude", StringComparison.OrdinalIgnoreCase))
|
|
||||||
.OrderByDescending(x => x.Timestamp)
|
|
||||||
.Take(3)
|
.Take(3)
|
||||||
.Select(x =>
|
.Select(x =>
|
||||||
{
|
{
|
||||||
@@ -3252,8 +3250,8 @@ public partial class SettingsWindow : Window
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
TxtMemoryOverviewAudit.Text = includeEntries.Count == 0
|
TxtMemoryOverviewAudit.Text = includeEntries.Count == 0
|
||||||
? "최근 include 감사 기록이 없습니다."
|
? "최근 3일 include 감사 기록이 없습니다."
|
||||||
: "최근 include 감사\n" + string.Join("\n", includeEntries);
|
: "최근 3일 include 감사\n" + string.Join("\n", includeEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 에이전트 훅 관리 ─────────────────────────────────────────────────
|
// ─── 에이전트 훅 관리 ─────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user