diff --git a/README.md b/README.md index e9cc0d0..a5f956d 100644 --- a/README.md +++ b/README.md @@ -1651,3 +1651,7 @@ MIT License - compact 이후 응답/컨텍스트 사용 표시에서 남아 있던 후행 운영 메타를 더 줄였습니다. - [ChatWindow.ResponsePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs)는 응답 하단 토큰 메타에서 `compact 직후` 꼬리표를 제거해, 일반 응답과 같은 밀도로 보이도록 정리했습니다. - [ChatWindow.ContextUsagePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ContextUsagePresentation.cs)는 token usage 팝업 detail에서 `compact 후 첫 응답 대기 중` 문구를 빼고 실제 컨텍스트/압축 정보만 보여주도록 단순화했습니다. +- 업데이트: 2026-04-12 22:36 (KST) + - `claw-code`의 post-compact attachments 흐름을 참고해, AX도 요약/경계 메시지에 오래된 첨부 참조를 다시 실어 compact 뒤 컨텍스트 연속성을 보강했습니다. + - [ContextCondenser.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ContextCondenser.cs)는 오래된 메시지에서 첨부 파일 이름과 이미지 개수를 수집해 `microcompact_boundary`와 요약 메시지에 함께 기록합니다. + - 요약 메시지에는 `AttachedFiles`도 같이 보존해, compact 이후에도 관련 파일 참조가 query view로 이어질 수 있게 맞췄습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 7245030..754b0ec 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -657,3 +657,14 @@ owKindCounts를 함께 남겨 %APPDATA%\\AxCopilot\\perf 기준으로 transcript - compact 이후에도 transcript와 usage UI가 일반 응답과 더 비슷한 밀도로 이어집니다. - 운영 상태는 내부 로직/통계에만 남기고, 사용자 화면은 더 `claw-code` 스타일의 얇은 표현에 가까워집니다. +## compact 후 첨부 참조 재주입 보강 (2026-04-12 22:36 KST) + +- `claw-code`의 `buildPostCompactMessages()`가 attachments를 boundary/summary 뒤에 다시 붙이는 흐름을 참고해, AX도 compact 이후 첨부 참조가 완전히 사라지지 않도록 보강했습니다. +- `src/AxCopilot/Services/Agent/ContextCondenser.cs` + - 오래된 메시지 구간에서 `AttachedFiles`와 `Images` 개수를 수집하는 helper를 추가했습니다. + - `BuildMicrocompactBoundary()`는 compact 경계 메시지에 관련 파일 목록과 이미지 개수를 함께 요약하고, `AttachedFiles`도 메타 메시지에 유지합니다. + - `SummarizeOldMessagesAsync()`는 요약 메시지 하단에 `참고 파일`, `참고 이미지` 줄을 추가하고, `AttachedFiles`도 요약 메시지에 보존합니다. +- 기대 효과 + - compact 이후에도 “이 대화가 어떤 파일/이미지를 참고했는지”가 요약 메시지에서 다시 드러납니다. + - query view가 compact 이후 메시지를 다시 보낼 때, 파일 참조 continuity가 이전보다 더 자연스럽게 유지됩니다. + diff --git a/src/AxCopilot/Services/Agent/ContextCondenser.cs b/src/AxCopilot/Services/Agent/ContextCondenser.cs index 3ebaf8a..4b052f3 100644 --- a/src/AxCopilot/Services/Agent/ContextCondenser.cs +++ b/src/AxCopilot/Services/Agent/ContextCondenser.cs @@ -54,6 +54,12 @@ public static class ContextCondenser public int PreservedToolPairs { get; init; } } + private sealed class CompactionAttachmentSummary + { + public required List AttachedFiles { get; init; } + public int ImageCount { get; init; } + } + /// 모델별 입력 토큰 한도 (대략). private static int GetModelInputLimit(string service, string model) { @@ -703,17 +709,7 @@ public static class ContextCondenser !(m.Content ?? "").StartsWith("{\"_tool_use_blocks\"", StringComparison.Ordinal) && m.MetaKind == null); - var attachedFiles = group - .SelectMany(m => m.AttachedFiles ?? Enumerable.Empty()) - .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(path => - { - try { return System.IO.Path.GetFileName(path); } - catch { return path; } - }) - .Where(name => !string.IsNullOrWhiteSpace(name)) - .Take(4) - .ToList(); + var attachmentSummary = CollectCompactionAttachmentSummary(group, 4); var lines = new List { @@ -724,7 +720,8 @@ public static class ContextCondenser if (toolCalls > 0) lines.Add($"- 도구 호출 {toolCalls}건 정리"); if (metaEvents > 0) lines.Add($"- 실행 메타/로그 {metaEvents}건 정리"); if (longTexts > 0) lines.Add($"- 긴 설명/출력 {longTexts}건 축약"); - if (attachedFiles.Count > 0) lines.Add($"- 관련 파일: {string.Join(", ", attachedFiles)}"); + if (attachmentSummary.AttachedFiles.Count > 0) lines.Add($"- 관련 파일: {string.Join(", ", attachmentSummary.AttachedFiles)}"); + if (attachmentSummary.ImageCount > 0) lines.Add($"- 관련 이미지: {attachmentSummary.ImageCount}개"); return new ChatMessage { @@ -733,6 +730,7 @@ public static class ContextCondenser Timestamp = group.Last().Timestamp, MetaKind = "microcompact_boundary", MetaRunId = group.Last().MetaRunId, + AttachedFiles = attachmentSummary.AttachedFiles.Count > 0 ? attachmentSummary.AttachedFiles : null, }; } @@ -747,6 +745,7 @@ public static class ContextCondenser var systemMsg = window.SystemMessage; var oldMessages = window.OldMessages; var recentMessages = window.RecentMessages; + var attachmentSummary = CollectCompactionAttachmentSummary(oldMessages, 6); if (oldMessages.Count < 3) return false; @@ -787,8 +786,9 @@ public static class ContextCondenser messages.Add(new ChatMessage { Role = "user", - Content = $"[이전 대화 요약 — {oldMessages.Count}개 메시지 압축]\n{summary}", + Content = BuildSummaryMessageContent(oldMessages.Count, summary, attachmentSummary), Timestamp = DateTime.Now, + AttachedFiles = attachmentSummary.AttachedFiles.Count > 0 ? attachmentSummary.AttachedFiles : null, }); messages.Add(new ChatMessage { @@ -806,4 +806,42 @@ public static class ContextCondenser return false; } } + + private static CompactionAttachmentSummary CollectCompactionAttachmentSummary(IEnumerable messages, int maxFiles) + { + var attachedFiles = messages + .SelectMany(m => m.AttachedFiles ?? Enumerable.Empty()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(path => + { + try { return System.IO.Path.GetFileName(path); } + catch { return path; } + }) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Take(Math.Max(1, maxFiles)) + .ToList(); + + var imageCount = messages.Sum(m => m.Images?.Count ?? 0); + return new CompactionAttachmentSummary + { + AttachedFiles = attachedFiles, + ImageCount = imageCount, + }; + } + + private static string BuildSummaryMessageContent(int oldMessageCount, string summary, CompactionAttachmentSummary attachmentSummary) + { + var lines = new List + { + $"[이전 대화 요약 — {oldMessageCount}개 메시지 압축]", + summary.Trim() + }; + + if (attachmentSummary.AttachedFiles.Count > 0) + lines.Add($"참고 파일: {string.Join(", ", attachmentSummary.AttachedFiles)}"); + if (attachmentSummary.ImageCount > 0) + lines.Add($"참고 이미지: {attachmentSummary.ImageCount}개"); + + return string.Join("\n", lines.Where(x => !string.IsNullOrWhiteSpace(x))); + } }