- 권한 요청 카탈로그를 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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 서버 연결 상태와 도구 등록 상태를 다시 확인하세요.",
|
||||
_ => "같은 요청을 재시도하기 전에 원인 메시지를 먼저 확인하세요.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user