도구·권한·스킬 표현 정교화 1차 반영
Some checks failed
Release Gate / gate (push) Has been cancelled

- 권한 요청 카탈로그를 bash/powershell/web_fetch/mcp/skill/file_edit/file_write/git/document/filesystem 수준으로 세분화했습니다.

- 도구 결과 카탈로그에 approval_required, partial, follow-up hint, attention 메타를 추가해 후속 renderer 고도화 기반을 마련했습니다.

- 스킬 갤러리에 모델, 추론 강도, 실행 컨텍스트, 에이전트, 모델 호출 비활성화, 추천 상황을 표시하도록 확장했습니다.

- README, DEVELOPMENT, parity plan, regression prompts 문서를 2026-04-06 11:52 (KST) 기준으로 갱신했습니다.

- 검증: 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 12:57:15 +09:00
parent f11b8b74b7
commit e747032501
7 changed files with 248 additions and 82 deletions

View File

@@ -5,8 +5,11 @@ internal sealed record PermissionRequestPresentation(
string Icon,
string Label,
string Description,
string ActionHint,
string BackgroundHex,
string ForegroundHex);
string ForegroundHex,
string Severity,
bool RequiresPreview);
internal static class PermissionRequestPresentationCatalog
{
@@ -14,88 +17,108 @@ internal static class PermissionRequestPresentationCatalog
{
var tool = (toolName ?? string.Empty).Trim().ToLowerInvariant();
if (tool.Contains("bash") || tool.Contains("powershell") || tool.Contains("process"))
return Build(
"command",
pending,
"\uE756",
"명령 실행 권한 요청",
"명령 실행 권한 확인",
"터미널 명령을 실행하기 전에 확인이 필요합니다.",
if (tool.Contains("bash"))
return Build("bash", pending, "\uE756",
"Bash 실행 권한 요청", "Bash 실행 확인",
"셸 명령을 실행하기 전에 확인이 필요합니다.",
"Bash 실행이 승인되어 계속 진행합니다.",
"명령과 작업 위치를 확인하세요.",
"#FEF2F2", "#DC2626", "high", false);
if (tool.Contains("powershell"))
return Build("powershell", pending, "\uE756",
"PowerShell 권한 요청", "PowerShell 실행 확인",
"PowerShell 명령을 실행하기 전에 확인이 필요합니다.",
"PowerShell 실행이 승인되어 계속 진행합니다.",
"스크립트와 실행 범위를 확인하세요.",
"#FEF2F2", "#DC2626", "high", false);
if (tool.Contains("process") || tool.Contains("build") || tool.Contains("test"))
return Build("command", pending, "\uE756",
"명령 실행 권한 요청", "명령 실행 확인",
"명령 또는 빌드 작업 실행 전에 확인이 필요합니다.",
"명령 실행이 승인되어 계속 진행합니다.",
"#FEF2F2",
"#DC2626");
"실행 명령과 영향 범위를 확인하세요.",
"#FEF2F2", "#DC2626", "high", false);
if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http"))
return Build(
"web",
pending,
"\uE774",
"웹 요청 권한 요청",
"웹 요청 권한 확인",
"외부 웹 요청 전 사용자 확인이 필요합니다.",
return Build("web_fetch", pending, "\uE774",
"웹 요청 권한 요청", "웹 요청 확인",
"외부 요청을 보내기 전에 확인이 필요합니다.",
"웹 요청이 승인되어 계속 진행합니다.",
"#FFF7ED",
"#C2410C");
"조회 URL과 전송 범위를 확인하세요.",
"#FFF7ED", "#C2410C", "medium", false);
if (tool.Contains("mcp"))
return Build("mcp", pending, "\uE943",
"MCP 도구 권한 요청", "MCP 도구 확인",
"연결된 MCP 도구 사용 전에 확인이 필요합니다.",
"MCP 도구 사용이 승인되어 계속 진행합니다.",
"서버와 호출 의도를 확인하세요.",
"#F5F3FF", "#7C3AED", "medium", false);
if (tool.Contains("skill"))
return Build(
"skill",
pending,
"\uE8A5",
"스킬 실행 권한 요청",
"스킬 실행 권한 확인",
"연결된 스킬 실행 전 확인이 필요합니다.",
return Build("skill", pending, "\uE8A5",
"스킬 실행 권한 요청", "스킬 실행 확인",
"연결된 스킬을 실행하기 전에 확인이 필요합니다.",
"스킬 실행이 승인되어 계속 진행합니다.",
"#F5F3FF",
"#7C3AED");
"허용 도구와 실행 컨텍스트를 확인하세요.",
"#F5F3FF", "#7C3AED", "medium", false);
if (tool.Contains("ask"))
return Build(
"question",
pending,
"\uE897",
"의견 요청 확인",
"의견 요청 완료",
return Build("question", pending, "\uE897",
"의견 요청 확인", "의견 요청 완료",
"사용자에게 선택이나 답변을 요청합니다.",
"사용자 의견을 받아 다음 단계로 진행합니다.",
"#EFF6FF",
"#2563EB");
"사용자 답변을 받아 다음 단계로 진행합니다.",
"질문 의도와 선택지를 확인하세요.",
"#EFF6FF", "#2563EB", "low", false);
if (tool.Contains("file_edit") || tool.Contains("file_write") || tool.Contains("edit"))
return Build(
"file_edit",
pending,
"\uE70F",
"파일 수정 권한 요청",
"파일 수정 권한 확인",
"파일을 만들거나 수정하기 전에 확인이 필요합니다.",
if (tool.Contains("file_edit") || tool.Contains("edit"))
return Build("file_edit", pending, "\uE70F",
"파일 수정 권한 요청", "파일 수정 확인",
"파일을 변경하기 전에 확인이 필요합니다.",
"파일 수정이 승인되어 계속 진행합니다.",
"#FFF7ED",
"#C2410C");
"변경 diff와 대상 파일을 확인하세요.",
"#FFF7ED", "#C2410C", "high", true);
if (tool.Contains("file_write") || tool.Contains("write"))
return Build("file_write", pending, "\uE70F",
"파일 쓰기 권한 요청", "파일 쓰기 확인",
"새 파일 작성 또는 덮어쓰기 전에 확인이 필요합니다.",
"파일 쓰기가 승인되어 계속 진행합니다.",
"작성 위치와 새 내용 미리보기를 확인하세요.",
"#FFF7ED", "#C2410C", "high", true);
if (tool.Contains("git"))
return Build("git", pending, "\uE8A7",
"Git 작업 권한 요청", "Git 작업 확인",
"브랜치나 커밋 상태를 바꾸기 전에 확인이 필요합니다.",
"Git 작업이 승인되어 계속 진행합니다.",
"브랜치와 변경 범위를 확인하세요.",
"#EFF6FF", "#2563EB", "medium", false);
if (tool.Contains("document") || tool.Contains("template") || tool.Contains("format"))
return Build("document", pending, "\uE8A5",
"문서 작업 권한 요청", "문서 작업 확인",
"문서 생성 또는 변환 작업 전에 확인이 필요합니다.",
"문서 작업이 승인되어 계속 진행합니다.",
"출력 형식과 저장 위치를 확인하세요.",
"#FFF7ED", "#C2410C", "medium", false);
if (tool.Contains("file") || tool.Contains("glob") || tool.Contains("grep") || tool.Contains("folder"))
return Build(
"file_access",
pending,
"\uE8A5",
"파일 접근 권한 요청",
"파일 접근 권한 확인",
"폴더와 파일 내용을 읽기 전에 확인이 필요합니다.",
return Build("filesystem", pending, "\uE8A5",
"파일 접근 권한 요청", "파일 접근 확인",
"폴더나 파일 내용을 읽기 전에 확인이 필요합니다.",
"파일 접근이 승인되어 계속 진행합니다.",
"#FFF7ED",
"#C2410C");
"읽기 범위와 접근 경로를 확인하세요.",
"#FFF7ED", "#C2410C", "medium", false);
return Build(
"generic",
pending,
"\uE897",
"권한 요청",
"권한 확인",
"계속 진행하기 전에 사용자 확인이 필요합니다.",
return Build("generic", pending, "\uE897",
"권한 요청", "권한 확인",
"계속 진행하기 전에 사용자의 확인이 필요합니다.",
"요청이 승인되어 계속 진행합니다.",
"#FFF7ED",
"#C2410C");
"실행 의도와 대상 범위를 확인하세요.",
"#FFF7ED", "#C2410C", "medium", false);
}
private static PermissionRequestPresentation Build(
@@ -106,15 +129,21 @@ internal static class PermissionRequestPresentationCatalog
string resolvedLabel,
string pendingDescription,
string resolvedDescription,
string actionHint,
string backgroundHex,
string foregroundHex)
string foregroundHex,
string severity,
bool requiresPreview)
{
return new PermissionRequestPresentation(
kind,
pending ? icon : "\uE73E",
pending ? pendingLabel : resolvedLabel,
pending ? pendingDescription : resolvedDescription,
actionHint,
pending ? backgroundHex : "#ECFDF5",
pending ? foregroundHex : "#059669");
pending ? foregroundHex : "#059669",
severity,
requiresPreview);
}
}

View File

@@ -5,9 +5,11 @@ internal sealed record ToolResultPresentation(
string Icon,
string Label,
string Description,
string FollowUpHint,
string BackgroundHex,
string ForegroundHex,
string StatusKind);
string StatusKind,
bool NeedsAttention);
internal static class ToolResultPresentationCatalog
{
@@ -27,9 +29,11 @@ internal static class ToolResultPresentationCatalog
"\uE711",
$"{baseLabel} 취소",
"요청이 중단되어 결과가 취소되었습니다.",
"필요하면 같은 요청을 다시 실행하세요.",
"#F8FAFC",
"#475569",
"cancel");
"cancel",
false);
}
if (summary.Contains("거부", StringComparison.OrdinalIgnoreCase) ||
@@ -41,9 +45,41 @@ internal static class ToolResultPresentationCatalog
"\uE783",
$"{baseLabel} 거부",
"권한이 거부되어 작업이 중단되었습니다.",
"권한 모드를 바꾸거나 다시 승인하면 이어서 진행할 수 있습니다.",
"#FEF2F2",
"#DC2626",
"reject");
"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)
@@ -53,9 +89,11 @@ internal static class ToolResultPresentationCatalog
"\uE783",
BuildFailureLabel(kind, baseLabel),
BuildFailureDescription(kind),
BuildFailureFollowUp(kind),
"#FEF2F2",
"#DC2626",
"error");
"error",
true);
}
return new ToolResultPresentation(
@@ -63,23 +101,35 @@ internal static class ToolResultPresentationCatalog
"\uE73E",
BuildSuccessLabel(kind, baseLabel),
BuildSuccessDescription(kind),
BuildSuccessFollowUp(kind),
"#ECFDF5",
"#16A34A",
"success");
"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";
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"))
@@ -91,11 +141,16 @@ internal static class ToolResultPresentationCatalog
{
return kind switch
{
"file_edit" => "파일 수정 완료",
"file_write" => "파일 쓰기 완료",
"filesystem" => "파일 탐색 완료",
"file" => "파일 작업 완료",
"build" => "빌드/테스트 완료",
"build_test" => "빌드/테스트 완료",
"git" => "Git 작업 완료",
"document" => "문서 작업 완료",
"skill" => "스킬 실행 완료",
"mcp" => "MCP 도구 완료",
"question" => "의견 요청 완료",
"web" => "웹 요청 완료",
"command" => "명령 실행 완료",
_ => baseLabel,
@@ -106,11 +161,16 @@ internal static class ToolResultPresentationCatalog
{
return kind switch
{
"file_edit" => "파일 수정 실패",
"file_write" => "파일 쓰기 실패",
"filesystem" => "파일 탐색 실패",
"file" => "파일 작업 실패",
"build" => "빌드/테스트 실패",
"build_test" => "빌드/테스트 실패",
"git" => "Git 작업 실패",
"document" => "문서 작업 실패",
"skill" => "스킬 실행 실패",
"mcp" => "MCP 도구 실패",
"question" => "의견 요청 실패",
"web" => "웹 요청 실패",
"command" => "명령 실행 실패",
_ => $"{baseLabel} 실패",
@@ -121,12 +181,17 @@ internal static class ToolResultPresentationCatalog
{
return kind switch
{
"file" => "파일 처리 결과가 정상적으로 반영되었습니다.",
"build" => "빌드나 테스트 단계가 성공적으로 끝났습니다.",
"git" => "Git 관련 작업이 정상적으로 완료되었습니다.",
"file_edit" => "파일 수정 결과가 저장되었습니다.",
"file_write" => "새 파일 작성 결과가 저장되었습니다.",
"filesystem" => "파일과 폴더 정보를 성공적으로 었습니다.",
"file" => "파일 관련 작업이 정상적으로 끝났습니다.",
"build_test" => "빌드 또는 테스트 단계가 성공적으로 끝났습니다.",
"git" => "Git 관련 작업이 정상적으로 끝났습니다.",
"document" => "문서 생성 또는 변환 작업이 완료되었습니다.",
"skill" => "선택 스킬이 정상적으로 실행되었습니다.",
"web" => "웹 요청이 정상적으로 완료되었습니다.",
"skill" => "선택 스킬이 정상적으로 실행되었습니다.",
"mcp" => "등록된 MCP 도구 호출이 성공적으로 끝났습니다.",
"question" => "사용자 응답을 받아 다음 단계로 넘어갈 수 있습니다.",
"web" => "웹 요청이 정상적으로 끝났습니다.",
"command" => "명령 실행이 정상적으로 끝났습니다.",
_ => "요청한 작업이 정상적으로 완료되었습니다.",
};
@@ -136,14 +201,47 @@ internal static class ToolResultPresentationCatalog
{
return kind switch
{
"file_edit" => "파일 변경 과정에서 문제가 발생했습니다.",
"file_write" => "파일 작성 또는 저장 과정에서 문제가 발생했습니다.",
"filesystem" => "파일/폴더 접근 중 문제가 발생했습니다.",
"file" => "파일 처리 중 문제가 발생했습니다.",
"build" => "빌드 테스트 단계에서 실패가 발생했습니다.",
"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 서버 연결 상태와 도구 등록 상태를 다시 확인하세요.",
_ => "같은 요청을 재시도하기 전에 원인 메시지를 먼저 확인하세요.",
};
}
}

View File

@@ -578,6 +578,18 @@ public partial class SkillGalleryWindow : Window
AddMetaRow("런타임", skill.Requires, metaRow++);
if (!string.IsNullOrEmpty(skill.AllowedTools))
AddMetaRow("허용 도구", skill.AllowedTools, metaRow++);
if (!string.IsNullOrEmpty(skill.Model))
AddMetaRow("모델", skill.Model, metaRow++);
if (!string.IsNullOrEmpty(skill.Effort))
AddMetaRow("추론 강도", skill.Effort, metaRow++);
if (!string.IsNullOrEmpty(skill.ExecutionContext))
AddMetaRow("실행 컨텍스트", skill.ExecutionContext, metaRow++);
if (!string.IsNullOrEmpty(skill.Agent))
AddMetaRow("에이전트", skill.Agent, metaRow++);
if (skill.DisableModelInvocation)
AddMetaRow("모델 호출", "비활성화", metaRow++);
if (!string.IsNullOrEmpty(skill.WhenToUse))
AddMetaRow("추천 상황", skill.WhenToUse, metaRow++);
AddMetaRow("상태", skill.IsAvailable ? "✓ 사용 가능" : $"✗ {skill.UnavailableHint}", metaRow++);
AddMetaRow("경로", skill.FilePath, metaRow++);