메모리 include 감사 로그와 AX Agent 메모리 상태 UX 강화
- AgentMemoryService에 @include 성공/차단 감사 로그(MemoryInclude) 기록 추가 - Cowork/Code 하단 폴더 바에 메모리 규칙/학습 메모리 상태 요약 표시 추가 - 설정의 외부 메모리 include 안내 문구를 감사 로그 기준으로 정리 - dotnet build 검증 완료 (경고 0 / 오류 0)
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user