AX Agent transcript 권한·도구 결과 표현 정교화 및 이벤트 렌더 분리
Some checks failed
Release Gate / gate (push) Has been cancelled

- claw-code 기준으로 transcript display catalog를 파일/문서/빌드/Git/웹/질문/에이전트 축으로 재정의
- 권한 요청 presentation catalog를 명령 실행, 웹 요청, 스킬 실행, 의견 요청, 파일 수정, 파일 접근 타입으로 세분화
- tool result catalog를 성공/실패/거부/취소와 도구 종류에 따라 더 읽기 쉬운 한국어 결과 라벨로 정리
- CreateCompactEventPill, AddAgentEventBanner, GetDecisionBadgeMeta를 ChatWindow.AgentEventRendering.cs로 분리해 메인 창 코드 비중 축소
- README와 DEVELOPMENT 문서에 변경 목적과 검증 결과 반영
- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\ (경고 0 / 오류 0)
This commit is contained in:
2026-04-06 06:59:22 +09:00
parent f9d18fba08
commit 95e40df354
7 changed files with 581 additions and 470 deletions

View File

@@ -1,3 +1,5 @@
using System;
namespace AxCopilot.Services.Agent;
internal static class AgentTranscriptDisplayCatalog
@@ -5,30 +7,44 @@ internal static class AgentTranscriptDisplayCatalog
public static string GetDisplayName(string? rawName, bool slashPrefix = false)
{
if (string.IsNullOrWhiteSpace(rawName))
return slashPrefix ? "/스킬" : "도구";
return slashPrefix ? "/skill" : "도구";
var normalized = rawName.Trim();
var mapped = normalized.ToLowerInvariant() switch
var lowered = normalized.ToLowerInvariant();
var mapped = lowered switch
{
"file_read" => "파일 읽기",
"file_write" => "파일 쓰기",
"file_edit" => "파일 편집",
"file_watch" => "파일 변경 감시",
"file_info" => "파일 정보",
"file_manage" => "파일 관리",
"glob" => "파일 찾기",
"grep" => "내용 검색",
"folder_map" => "폴더 구조",
"document_reader" => "문서 읽기",
"document_planner" => "문서 계획",
"document_assembler" => "문서 조합",
"document_review" => "문서 검토",
"format_convert" => "형식 변환",
"code_search" => "코드 검색",
"code_review" => "코드 리뷰",
"template_render" => "템플릿 렌더",
"build_run" => "빌드/실행",
"test_loop" => "테스트 루프",
"dev_env_detect" => "개발 환경 점검",
"git_tool" => "Git",
"process" => "프로세스",
"glob" => "파일 찾기",
"grep" => "내용 검색",
"folder_map" => "폴더 맵",
"memory" => "메모리",
"diff_tool" => "Diff",
"diff_preview" => "Diff 미리보기",
"process" => "명령 실행",
"bash" => "Bash",
"powershell" => "PowerShell",
"web_fetch" => "웹 요청",
"http" => "HTTP 요청",
"user_ask" => "의견 요청",
"suggest_actions" => "다음 작업 제안",
"task_create" => "작업 생성",
"task_update" => "작업 업데이트",
"task_list" => "작업 목록",
@@ -43,7 +59,10 @@ internal static class AgentTranscriptDisplayCatalog
if (!slashPrefix)
return mapped;
return normalized.StartsWith('/') ? normalized : "/" + normalized.Replace(' ', '-');
if (normalized.StartsWith('/'))
return normalized;
return "/" + lowered.Replace('_', '-').Replace(' ', '-');
}
public static string GetEventBadgeLabel(AgentEvent evt)
@@ -59,22 +78,23 @@ internal static class AgentTranscriptDisplayCatalog
public static string GetTaskCategoryLabel(string? kind, string? title)
{
if (string.Equals(kind, "permission", System.StringComparison.OrdinalIgnoreCase))
if (string.Equals(kind, "permission", StringComparison.OrdinalIgnoreCase))
return "권한";
if (string.Equals(kind, "queue", System.StringComparison.OrdinalIgnoreCase))
return "";
if (string.Equals(kind, "hook", System.StringComparison.OrdinalIgnoreCase))
if (string.Equals(kind, "queue", StringComparison.OrdinalIgnoreCase))
return "대기열";
if (string.Equals(kind, "hook", StringComparison.OrdinalIgnoreCase))
return "훅";
if (string.Equals(kind, "subagent", System.StringComparison.OrdinalIgnoreCase))
if (string.Equals(kind, "subagent", StringComparison.OrdinalIgnoreCase))
return "에이전트";
if (string.Equals(kind, "tool", System.StringComparison.OrdinalIgnoreCase))
if (string.Equals(kind, "tool", StringComparison.OrdinalIgnoreCase))
return GetToolCategoryLabel(title);
return "작업";
}
public static string BuildEventSummary(AgentEvent evt, string displayName)
{
var summary = (evt.Summary ?? "").Trim();
var summary = (evt.Summary ?? string.Empty).Trim();
if (!string.IsNullOrWhiteSpace(summary))
return summary;
@@ -83,9 +103,11 @@ internal static class AgentTranscriptDisplayCatalog
AgentEventType.ToolCall => $"{displayName} 실행 준비",
AgentEventType.ToolResult => evt.Success ? $"{displayName} 실행 완료" : $"{displayName} 실행 실패",
AgentEventType.SkillCall => $"{displayName} 실행",
AgentEventType.PermissionRequest => $"{displayName} 실행 전 사용자 확인 필요",
AgentEventType.PermissionGranted => $"{displayName} 실행이 허용됨",
AgentEventType.PermissionDenied => $"{displayName} 실행이 거부",
AgentEventType.PermissionRequest => $"{displayName} 실행 전에 권한 확인 필요합니다.",
AgentEventType.PermissionGranted => $"{displayName} 실행 권한이 승인되었습니다.",
AgentEventType.PermissionDenied => $"{displayName} 실행 권한이 거부되었습니다.",
AgentEventType.Complete => "에이전트 작업이 완료되었습니다.",
AgentEventType.Error => "에이전트 실행 중 오류가 발생했습니다.",
_ => summary,
};
}
@@ -97,14 +119,24 @@ internal static class AgentTranscriptDisplayCatalog
return rawName.Trim().ToLowerInvariant() switch
{
"file_read" or "file_write" or "file_edit" or "glob" or "grep" or "folder_map" or "file_watch" or "file_info" or "file_manage" => "파일",
"build_run" or "test_loop" or "dev_env_detect" => "빌드",
"git_tool" or "diff_tool" or "diff_preview" => "Git",
"document_reader" or "document_planner" or "document_assembler" or "document_review" or "format_convert" or "template_render" => "문서",
"user_ask" => "질문",
"suggest_actions" => "제안",
"process" => "실행",
"spawn_agent" or "wait_agents" => "에이전트",
"file_read" or "file_write" or "file_edit" or "glob" or "grep" or "folder_map" or "file_watch" or "file_info" or "file_manage"
=> "파일",
"build_run" or "test_loop" or "dev_env_detect"
=> "빌드",
"git_tool" or "diff_tool" or "diff_preview"
=> "Git",
"document_reader" or "document_planner" or "document_assembler" or "document_review" or "format_convert" or "template_render"
=> "문서",
"user_ask"
=> "질문",
"suggest_actions"
=> "제안",
"process" or "bash" or "powershell"
=> "명령",
"spawn_agent" or "wait_agents"
=> "에이전트",
"web_fetch" or "http"
=> "웹",
_ => "도구",
};
}

View File

@@ -1,3 +1,5 @@
using System;
namespace AxCopilot.Services.Agent;
internal sealed record PermissionRequestPresentation(
@@ -10,31 +12,52 @@ internal static class PermissionRequestPresentationCatalog
{
public static PermissionRequestPresentation Resolve(string? toolName, bool pending)
{
var tool = toolName?.Trim().ToLowerInvariant() ?? "";
var tool = (toolName ?? string.Empty).Trim().ToLowerInvariant();
if (tool.Contains("process") || tool.Contains("bash") || tool.Contains("powershell"))
if (tool.Contains("bash") || tool.Contains("powershell") || tool.Contains("process"))
{
return pending
? new PermissionRequestPresentation("\uE756", "명령 권한 요청", "#FEF2F2", "#DC2626")
: new PermissionRequestPresentation("\uE73E", "명령 권한 허용", "#ECFDF5", "#059669");
? new PermissionRequestPresentation("\uE756", "명령 실행 권한 요청", "#FEF2F2", "#DC2626")
: new PermissionRequestPresentation("\uE73E", "명령 실행 권한 승인", "#ECFDF5", "#059669");
}
if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http"))
{
return pending
? new PermissionRequestPresentation("\uE774", "네트워크 권한 요청", "#FFF7ED", "#C2410C")
: new PermissionRequestPresentation("\uE73E", "네트워크 권한 허용", "#ECFDF5", "#059669");
? new PermissionRequestPresentation("\uE774", "웹 요청 권한 요청", "#FFF7ED", "#C2410C")
: new PermissionRequestPresentation("\uE73E", "웹 요청 권한 승인", "#ECFDF5", "#059669");
}
if (tool.Contains("file"))
if (tool.Contains("skill"))
{
return pending
? new PermissionRequestPresentation("\uE8A5", "파일 권한 요청", "#FFF7ED", "#C2410C")
: new PermissionRequestPresentation("\uE73E", "파일 권한 허용", "#ECFDF5", "#059669");
? new PermissionRequestPresentation("\uE8A5", "스킬 실행 권한 요청", "#F5F3FF", "#7C3AED")
: new PermissionRequestPresentation("\uE73E", "스킬 실행 권한 승인", "#ECFDF5", "#059669");
}
if (tool.Contains("ask"))
{
return pending
? new PermissionRequestPresentation("\uE897", "의견 요청 권한 확인", "#EFF6FF", "#2563EB")
: new PermissionRequestPresentation("\uE73E", "의견 요청 권한 승인", "#ECFDF5", "#059669");
}
if (tool.Contains("file_edit") || tool.Contains("file_write") || tool.Contains("edit"))
{
return pending
? new PermissionRequestPresentation("\uE70F", "파일 수정 권한 요청", "#FFF7ED", "#C2410C")
: new PermissionRequestPresentation("\uE73E", "파일 수정 권한 승인", "#ECFDF5", "#059669");
}
if (tool.Contains("file") || tool.Contains("glob") || tool.Contains("grep") || tool.Contains("folder"))
{
return pending
? new PermissionRequestPresentation("\uE8A5", "파일 접근 권한 요청", "#FFF7ED", "#C2410C")
: new PermissionRequestPresentation("\uE73E", "파일 접근 권한 승인", "#ECFDF5", "#059669");
}
return pending
? new PermissionRequestPresentation("\uE897", "권한 요청", "#FFF7ED", "#C2410C")
: new PermissionRequestPresentation("\uE73E", "권한 허용", "#ECFDF5", "#059669");
: new PermissionRequestPresentation("\uE73E", "권한 승인", "#ECFDF5", "#059669");
}
}

View File

@@ -1,3 +1,5 @@
using System;
namespace AxCopilot.Services.Agent;
internal sealed record ToolResultPresentation(
@@ -11,27 +13,79 @@ internal static class ToolResultPresentationCatalog
{
public static ToolResultPresentation Resolve(AgentEvent evt, string fallbackLabel)
{
var summary = evt.Summary?.Trim() ?? "";
var summary = (evt.Summary ?? string.Empty).Trim();
var tool = (evt.ToolName ?? string.Empty).Trim().ToLowerInvariant();
var baseLabel = string.IsNullOrWhiteSpace(fallbackLabel) ? "도구 결과" : fallbackLabel;
if (summary.Contains("취소", StringComparison.OrdinalIgnoreCase) ||
summary.Contains("중단", StringComparison.OrdinalIgnoreCase) ||
evt.Type == AgentEventType.StopRequested)
{
return new ToolResultPresentation("\uE711", "도구 취소", "#F8FAFC", "#475569", "cancel");
return new ToolResultPresentation("\uE711", $"{baseLabel} 취소", "#F8FAFC", "#475569", "cancel");
}
if (summary.Contains("거부", StringComparison.OrdinalIgnoreCase) ||
summary.Contains("반려", StringComparison.OrdinalIgnoreCase) ||
summary.Contains("권한 거부", StringComparison.OrdinalIgnoreCase))
{
return new ToolResultPresentation("\uE783", "도구 거부", "#FEF2F2", "#DC2626", "reject");
return new ToolResultPresentation("\uE783", $"{baseLabel} 거부", "#FEF2F2", "#DC2626", "reject");
}
if (!evt.Success || evt.Type == AgentEventType.Error)
{
return new ToolResultPresentation("\uE783", "도구 실패", "#FEF2F2", "#DC2626", "error");
return new ToolResultPresentation(
"\uE783",
BuildFailureLabel(tool, baseLabel),
"#FEF2F2",
"#DC2626",
"error");
}
return new ToolResultPresentation("\uE73E", fallbackLabel, "#ECFDF5", "#16A34A", "success");
return new ToolResultPresentation(
"\uE73E",
BuildSuccessLabel(tool, baseLabel),
"#ECFDF5",
"#16A34A",
"success");
}
private static string BuildSuccessLabel(string tool, string baseLabel)
{
if (tool.Contains("file"))
return "파일 작업 완료";
if (tool.Contains("build") || tool.Contains("test"))
return "빌드/테스트 완료";
if (tool.Contains("git") || tool.Contains("diff"))
return "Git 작업 완료";
if (tool.Contains("document") || tool.Contains("format") || tool.Contains("template"))
return "문서 작업 완료";
if (tool.Contains("skill"))
return "스킬 실행 완료";
if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http"))
return "웹 요청 완료";
if (tool.Contains("process") || tool.Contains("bash") || tool.Contains("powershell"))
return "명령 실행 완료";
return baseLabel;
}
private static string BuildFailureLabel(string tool, string baseLabel)
{
if (tool.Contains("file"))
return "파일 작업 실패";
if (tool.Contains("build") || tool.Contains("test"))
return "빌드/테스트 실패";
if (tool.Contains("git") || tool.Contains("diff"))
return "Git 작업 실패";
if (tool.Contains("document") || tool.Contains("format") || tool.Contains("template"))
return "문서 작업 실패";
if (tool.Contains("skill"))
return "스킬 실행 실패";
if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http"))
return "웹 요청 실패";
if (tool.Contains("process") || tool.Contains("bash") || tool.Contains("powershell"))
return "명령 실행 실패";
return $"{baseLabel} 실패";
}
}