namespace AxCopilot.Services.Agent; internal sealed record ToolResultPresentation( string Kind, string Icon, string Label, string Description, string FollowUpHint, string BackgroundHex, string ForegroundHex, string StatusKind, bool NeedsAttention); internal static class ToolResultPresentationCatalog { public static ToolResultPresentation Resolve(AgentEvent evt, string fallbackLabel) { var summary = (evt.Summary ?? string.Empty).Trim(); var tool = (evt.ToolName ?? string.Empty).Trim().ToLowerInvariant(); var baseLabel = string.IsNullOrWhiteSpace(fallbackLabel) ? "도구 결과" : fallbackLabel; var kind = ResolveKind(tool); if (summary.Contains("취소", StringComparison.OrdinalIgnoreCase) || summary.Contains("중단", StringComparison.OrdinalIgnoreCase) || evt.Type == AgentEventType.StopRequested) { return new ToolResultPresentation( "cancel", "\uE711", $"{baseLabel} 취소", "요청이 중단되어 결과가 취소되었습니다.", "필요하면 같은 요청을 다시 실행하세요.", "#F8FAFC", "#475569", "cancel", false); } if (summary.Contains("거부", StringComparison.OrdinalIgnoreCase) || summary.Contains("반려", StringComparison.OrdinalIgnoreCase) || summary.Contains("권한 거부", StringComparison.OrdinalIgnoreCase)) { return new ToolResultPresentation( "reject", "\uE783", $"{baseLabel} 거부", "권한이 거부되어 작업이 중단되었습니다.", "권한 모드를 바꾸거나 다시 승인하면 이어서 진행할 수 있습니다.", "#FEF2F2", "#DC2626", "reject", true); } if (summary.Contains("승인 필요", StringComparison.OrdinalIgnoreCase) || summary.Contains("확인 필요", StringComparison.OrdinalIgnoreCase)) { return new ToolResultPresentation( kind, "\uE8D7", $"{baseLabel} 승인 대기", "다음 단계로 진행하려면 사용자 승인이 필요합니다.", "승인 후 같은 작업 흐름이 이어집니다.", "#FFF7ED", "#C2410C", "approval_required", true); } if (summary.Contains("부분", StringComparison.OrdinalIgnoreCase) || summary.Contains("일부", StringComparison.OrdinalIgnoreCase)) { return new ToolResultPresentation( kind, "\uE7BA", $"{baseLabel} 부분 완료", "일부 단계만 완료되어 후속 확인이나 재실행이 필요할 수 있습니다.", "남은 단계나 누락된 결과를 확인하세요.", "#FFFBEA", "#A16207", "partial", true); } if (!evt.Success || evt.Type == AgentEventType.Error) { return new ToolResultPresentation( kind, "\uE783", BuildFailureLabel(kind, baseLabel), BuildFailureDescription(kind), BuildFailureFollowUp(kind), "#FEF2F2", "#DC2626", "error", true); } return new ToolResultPresentation( kind, "\uE73E", BuildSuccessLabel(kind, baseLabel), BuildSuccessDescription(kind), BuildSuccessFollowUp(kind), "#ECFDF5", "#16A34A", "success", false); } private static string ResolveKind(string tool) { if (tool.Contains("file_edit")) return "file_edit"; if (tool.Contains("file_write")) return "file_write"; if (tool.Contains("file_read") || tool.Contains("glob") || tool.Contains("grep")) return "filesystem"; if (tool.Contains("file")) return "file"; if (tool.Contains("build") || tool.Contains("test")) return "build_test"; if (tool.Contains("git") || tool.Contains("diff")) return "git"; if (tool.Contains("document") || tool.Contains("format") || tool.Contains("template")) return "document"; if (tool.Contains("skill")) return "skill"; if (tool.Contains("mcp")) return "mcp"; if (tool.Contains("ask")) return "question"; if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http")) return "web"; if (tool.Contains("process") || tool.Contains("bash") || tool.Contains("powershell")) return "command"; return "generic"; } private static string BuildSuccessLabel(string kind, string baseLabel) { return kind switch { "file_edit" => "파일 수정 완료", "file_write" => "파일 쓰기 완료", "filesystem" => "파일 탐색 완료", "file" => "파일 작업 완료", "build_test" => "빌드/테스트 완료", "git" => "Git 작업 완료", "document" => "문서 작업 완료", "skill" => "스킬 실행 완료", "mcp" => "MCP 도구 완료", "question" => "의견 요청 완료", "web" => "웹 요청 완료", "command" => "명령 실행 완료", _ => baseLabel, }; } private static string BuildFailureLabel(string kind, string baseLabel) { return kind switch { "file_edit" => "파일 수정 실패", "file_write" => "파일 쓰기 실패", "filesystem" => "파일 탐색 실패", "file" => "파일 작업 실패", "build_test" => "빌드/테스트 실패", "git" => "Git 작업 실패", "document" => "문서 작업 실패", "skill" => "스킬 실행 실패", "mcp" => "MCP 도구 실패", "question" => "의견 요청 실패", "web" => "웹 요청 실패", "command" => "명령 실행 실패", _ => $"{baseLabel} 실패", }; } private static string BuildSuccessDescription(string kind) { return kind switch { "file_edit" => "파일 수정 결과가 저장되었습니다.", "file_write" => "새 파일 작성 결과가 저장되었습니다.", "filesystem" => "파일과 폴더 정보를 성공적으로 읽었습니다.", "file" => "파일 관련 작업이 정상적으로 끝났습니다.", "build_test" => "빌드 또는 테스트 단계가 성공적으로 끝났습니다.", "git" => "Git 관련 작업이 정상적으로 끝났습니다.", "document" => "문서 생성 또는 변환 작업이 완료되었습니다.", "skill" => "선택한 스킬이 정상적으로 실행되었습니다.", "mcp" => "등록된 MCP 도구 호출이 성공적으로 끝났습니다.", "question" => "사용자 응답을 받아 다음 단계로 넘어갈 수 있습니다.", "web" => "웹 요청이 정상적으로 끝났습니다.", "command" => "명령 실행이 정상적으로 끝났습니다.", _ => "요청한 작업이 정상적으로 완료되었습니다.", }; } private static string BuildFailureDescription(string kind) { return kind switch { "file_edit" => "파일 변경 과정에서 문제가 발생했습니다.", "file_write" => "파일 작성 또는 저장 과정에서 문제가 발생했습니다.", "filesystem" => "파일/폴더 접근 중 문제가 발생했습니다.", "file" => "파일 처리 중 문제가 발생했습니다.", "build_test" => "빌드 또는 테스트 단계에서 실패가 발생했습니다.", "git" => "Git 관련 작업이 실패했습니다.", "document" => "문서 생성 또는 변환 작업이 실패했습니다.", "skill" => "스킬 실행 중 문제가 발생했습니다.", "mcp" => "MCP 도구 호출 중 문제가 발생했습니다.", "question" => "사용자 의견 요청 과정에서 문제가 발생했습니다.", "web" => "웹 요청 처리에 실패했습니다.", "command" => "명령 실행 중 오류가 발생했습니다.", _ => "작업 처리 중 오류가 발생했습니다.", }; } private static string BuildSuccessFollowUp(string kind) { return kind switch { "file_edit" or "file_write" => "변경 내용을 preview나 diff에서 다시 확인할 수 있습니다.", "build_test" => "출력 로그와 후속 수정 필요 여부를 확인하세요.", "git" => "브랜치 상태나 변경 요약을 이어서 확인하세요.", "document" => "생성된 산출물 경로를 열어 결과를 확인하세요.", "skill" => "같은 스킬을 다른 입력으로 이어서 실행할 수 있습니다.", _ => "필요하면 후속 요청을 이어서 실행할 수 있습니다.", }; } private static string BuildFailureFollowUp(string kind) { return kind switch { "file_edit" or "file_write" => "대상 파일 경로와 권한, diff를 다시 확인하세요.", "build_test" => "실패 로그와 컴파일 오류 메시지를 먼저 확인하세요.", "git" => "현재 브랜치, 잠금 상태, 충돌 여부를 확인하세요.", "document" => "입력 데이터와 출력 형식, 저장 위치를 다시 확인하세요.", "skill" => "허용 도구와 런타임 요구사항을 다시 확인하세요.", "web" => "연결 상태와 요청 대상 URL을 다시 확인하세요.", "mcp" => "MCP 서버 연결 상태와 도구 등록 상태를 다시 확인하세요.", _ => "같은 요청을 재시도하기 전에 원인 메시지를 먼저 확인하세요.", }; } }