tool_result preview 복원과 슬래시 명령 합성 일원화

목적:
- 긴 세션, 분기, 재시작 이후에도 tool_result preview 축약 상태를 더 안정적으로 유지합니다.
- 슬래시 팔레트와 실제 /토큰 실행 해석이 어긋나지 않도록 built-in command와 skill 우선순위를 같은 규칙으로 맞춥니다.

핵심 수정:
- AgentMessageInvariantHelper에 tool_use_id 기준 preview 맵/복원 helper를 추가했습니다.
- ChatSessionStateService는 분기 대화 생성 시 QueryPreviewContent를 함께 복사하고, 저장된 대화를 다시 열 때 누락된 preview를 복원합니다.
- ChatStorageService는 저장 직전에 누락된 tool_result preview를 먼저 채워 재시작 후 축약 상태가 흔들리지 않게 정리했습니다.
- SlashCommandCatalog에 exact token 충돌 해석용 ResolvePreferredCommand를 추가하고, ChatWindow.ParseSlashCommandAsync가 built-in/skill 후보를 함께 모아 같은 우선순위 규칙으로 실행 대상을 선택하도록 맞췄습니다.
- SlashCommandCatalogTests를 새로 추가하고 ChatSessionStateServiceTests를 확장해 preview 복원과 skill 우선 해석을 회귀 검증했습니다.
- README.md, docs/DEVELOPMENT.md를 2026-04-15 07:16 (KST) 기준으로 갱신했습니다.

검증:
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_preview_state\\ -p:IntermediateOutputPath=obj\\verify_preview_state\\
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentToolResultBudgetTests|ChatSessionStateServiceTests" -p:OutputPath=bin\\verify_preview_state_tests\\ -p:IntermediateOutputPath=obj\\verify_preview_state_tests\\ (통과 38)
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_command_resolution\\ -p:IntermediateOutputPath=obj\\verify_command_resolution\\
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SlashCommandCatalogTests|ChatSessionStateServiceTests|AgentToolResultBudgetTests|AgentCommandQueueTests" -p:OutputPath=bin\\verify_command_resolution_tests\\ -p:IntermediateOutputPath=obj\\verify_command_resolution_tests\\ (통과 50)
This commit is contained in:
2026-04-15 07:18:40 +09:00
parent 07fd2267cb
commit 7c138f8ed9
10 changed files with 240 additions and 27 deletions

View File

@@ -642,7 +642,7 @@ public class ChatSessionStateServiceTests
],
Messages =
[
new ChatMessage { Role = "user", Content = "u1", Timestamp = DateTime.Now.AddMinutes(-3) },
new ChatMessage { Role = "user", Content = "u1", Timestamp = DateTime.Now.AddMinutes(-3), QueryPreviewContent = "preview-1" },
new ChatMessage { Role = "assistant", Content = "a1", Timestamp = DateTime.Now.AddMinutes(-2), MetaKind = "meta", MetaRunId = "run-1" },
new ChatMessage { Role = "user", Content = "u2", Timestamp = DateTime.Now.AddMinutes(-1) }
]
@@ -655,11 +655,49 @@ public class ChatSessionStateServiceTests
branch.WorkFolder.Should().Be(@"E:\workspace");
branch.BranchLabel.Should().Contain("2");
branch.Messages.Should().HaveCount(3);
branch.Messages[0].QueryPreviewContent.Should().Be("preview-1");
branch.Messages[1].MetaRunId.Should().Be("run-1");
branch.Messages[2].MetaKind.Should().Be("branch_context");
branch.AgentRunHistory.Should().ContainSingle();
}
[Fact]
public void LoadOrCreateConversation_RestoresMissingToolResultPreviewFromPersistedMessages()
{
var storage = new ChatStorageService();
var settings = new SettingsService();
var conversationId = $"conv-preview-{Guid.NewGuid():N}";
var conversation = new ChatConversation
{
Id = conversationId,
Tab = "Code",
Title = "preview restore",
Messages =
[
new ChatMessage
{
Role = "user",
Content = """{"type":"tool_result","tool_use_id":"call-restore","tool_name":"file_read","content":"long output"}""",
QueryPreviewContent = """{"type":"tool_result","tool_use_id":"call-restore","tool_name":"file_read","content":"preview"}"""
},
new ChatMessage
{
Role = "user",
Content = """{"type":"tool_result","tool_use_id":"call-restore","tool_name":"file_read","content":"long output"}"""
}
]
};
storage.Save(conversation);
var session = new ChatSessionStateService();
session.RememberConversation("Code", conversationId);
var loaded = session.LoadOrCreateConversation("Code", storage, settings);
loaded.Messages.Should().HaveCount(2);
loaded.Messages[1].QueryPreviewContent.Should().Be(loaded.Messages[0].QueryPreviewContent);
}
[Fact]
public void DraftStateHelpers_SelectAndTransitionQueuedItems()
{