권한 모드 동등화: Plan 추가 및 claw-code 권한 별칭 정규화 반영
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- 전역 권한 모드를 Ask/Plan/Auto/Deny 4단계로 확장 - claw-code 계열 권한 값(default/acceptEdits/dontAsk/bypassPermissions) 입력 시 내부 모드로 정규화 - AgentContext 권한 판정 경로(전역/도구 오버라이드/패턴 오버라이드) 정규화 적용 - Chat/Settings UI에서 Plan 모드 노출 및 인라인 순환(Ask->Plan->Auto->Deny) 반영 - AppState/SettingsViewModel/SettingsService에 권한값 정규화 및 저장 시 일관성 적용 - Permission lifecycle 이벤트 메시지에 유효 모드 표기 보강 - 빌드/테스트 검증: dotnet build 경고0 오류0, dotnet test 372/372 통과
This commit is contained in:
@@ -4068,6 +4068,9 @@ public partial class AgentLoopService
|
||||
case "ask":
|
||||
normalized = "ask";
|
||||
return true;
|
||||
case "plan":
|
||||
normalized = "ask";
|
||||
return true;
|
||||
case "auto":
|
||||
normalized = "auto";
|
||||
return true;
|
||||
@@ -4153,16 +4156,16 @@ public partial class AgentLoopService
|
||||
messages,
|
||||
success: true);
|
||||
|
||||
var effectivePerm = context.GetEffectiveToolPermission(toolName, target);
|
||||
var effectivePerm = PermissionModeCatalog.NormalizeGlobalMode(context.GetEffectiveToolPermission(toolName, target));
|
||||
|
||||
if (string.Equals(effectivePerm, "Ask", StringComparison.OrdinalIgnoreCase))
|
||||
EmitEvent(AgentEventType.PermissionRequest, toolName, $"권한 확인 필요 · 대상: {target}");
|
||||
if (PermissionModeCatalog.RequiresUserApproval(effectivePerm))
|
||||
EmitEvent(AgentEventType.PermissionRequest, toolName, $"권한 확인 필요({effectivePerm}) · 대상: {target}");
|
||||
|
||||
var allowed = await context.CheckToolPermissionAsync(toolName, target);
|
||||
if (allowed)
|
||||
{
|
||||
if (string.Equals(effectivePerm, "Ask", StringComparison.OrdinalIgnoreCase))
|
||||
EmitEvent(AgentEventType.PermissionGranted, toolName, $"권한 승인됨 · 대상: {target}");
|
||||
if (PermissionModeCatalog.RequiresUserApproval(effectivePerm))
|
||||
EmitEvent(AgentEventType.PermissionGranted, toolName, $"권한 승인됨({effectivePerm}) · 대상: {target}");
|
||||
await RunPermissionLifecycleHooksAsync(
|
||||
"__permission_granted__",
|
||||
"post",
|
||||
|
||||
@@ -98,7 +98,7 @@ public class AgentContext
|
||||
/// <summary>작업 폴더 경로.</summary>
|
||||
public string WorkFolder { get; set; } = "";
|
||||
|
||||
/// <summary>파일 접근 권한. Ask | Auto | Deny</summary>
|
||||
/// <summary>파일 접근 권한. Ask | Plan | Auto | Deny</summary>
|
||||
public string Permission { get; init; } = "Ask";
|
||||
|
||||
/// <summary>도구별 권한 오버라이드. 키: 도구명, 값: "ask" | "auto" | "deny".</summary>
|
||||
@@ -192,19 +192,21 @@ public class AgentContext
|
||||
public string GetEffectiveToolPermission(string toolName, string? target)
|
||||
{
|
||||
if (TryResolvePatternPermission(toolName, target, out var patternPermission))
|
||||
return patternPermission;
|
||||
return PermissionModeCatalog.NormalizeToolOverride(patternPermission);
|
||||
|
||||
if (ToolPermissions.TryGetValue(toolName, out var toolPerm) &&
|
||||
!string.IsNullOrWhiteSpace(toolPerm))
|
||||
return toolPerm;
|
||||
return PermissionModeCatalog.NormalizeToolOverride(toolPerm);
|
||||
if (ToolPermissions.TryGetValue("*", out var wildcardPerm) &&
|
||||
!string.IsNullOrWhiteSpace(wildcardPerm))
|
||||
return wildcardPerm;
|
||||
return PermissionModeCatalog.NormalizeToolOverride(wildcardPerm);
|
||||
if (ToolPermissions.TryGetValue("default", out var defaultPerm) &&
|
||||
!string.IsNullOrWhiteSpace(defaultPerm))
|
||||
return defaultPerm;
|
||||
return PermissionModeCatalog.NormalizeToolOverride(defaultPerm);
|
||||
|
||||
return SensitiveTools.Contains(toolName) ? Permission : "Auto";
|
||||
return SensitiveTools.Contains(toolName)
|
||||
? PermissionModeCatalog.NormalizeGlobalMode(Permission)
|
||||
: PermissionModeCatalog.Auto;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckToolPermissionAsync(string toolName, string target)
|
||||
@@ -213,9 +215,9 @@ public class AgentContext
|
||||
&& AxCopilot.Services.OperationModePolicy.IsBlockedAgentToolInInternalMode(toolName, target))
|
||||
return false;
|
||||
|
||||
var effectivePerm = GetEffectiveToolPermission(toolName, target);
|
||||
if (string.Equals(effectivePerm, "Deny", StringComparison.OrdinalIgnoreCase)) return false;
|
||||
if (string.Equals(effectivePerm, "Auto", StringComparison.OrdinalIgnoreCase)) return true;
|
||||
var effectivePerm = PermissionModeCatalog.NormalizeGlobalMode(GetEffectiveToolPermission(toolName, target));
|
||||
if (PermissionModeCatalog.IsDeny(effectivePerm)) return false;
|
||||
if (PermissionModeCatalog.IsAuto(effectivePerm)) return true;
|
||||
if (AskPermission == null) return false;
|
||||
|
||||
var normalizedTarget = string.IsNullOrWhiteSpace(target) ? toolName : target.Trim();
|
||||
|
||||
72
src/AxCopilot/Services/Agent/PermissionModeCatalog.cs
Normal file
72
src/AxCopilot/Services/Agent/PermissionModeCatalog.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// AX Agent permission mode constants and normalization helpers.
|
||||
/// Accepts legacy Ask/Auto/Deny values plus claw-code style aliases.
|
||||
/// </summary>
|
||||
public static class PermissionModeCatalog
|
||||
{
|
||||
public const string Ask = "Ask";
|
||||
public const string Plan = "Plan";
|
||||
public const string Auto = "Auto";
|
||||
public const string Deny = "Deny";
|
||||
|
||||
public static readonly IReadOnlyList<string> UserSelectableModes = new[]
|
||||
{
|
||||
Ask,
|
||||
Plan,
|
||||
Auto,
|
||||
Deny,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Normalize global permission mode.
|
||||
/// Supported aliases: ask/auto/deny/plan plus default, acceptEdits, dontAsk, bypassPermissions.
|
||||
/// </summary>
|
||||
public static string NormalizeGlobalMode(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return Ask;
|
||||
|
||||
return value.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"ask" => Ask,
|
||||
"default" => Ask,
|
||||
"plan" => Plan,
|
||||
"auto" => Auto,
|
||||
"acceptedits" => Auto,
|
||||
"dontask" => Auto,
|
||||
"deny" => Deny,
|
||||
"bypasspermissions" => Deny,
|
||||
_ => Ask,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize tool override permission.
|
||||
/// Tool override is constrained to ask/auto/deny.
|
||||
/// </summary>
|
||||
public static string NormalizeToolOverride(string? value)
|
||||
{
|
||||
var mode = NormalizeGlobalMode(value);
|
||||
return mode switch
|
||||
{
|
||||
Auto => "auto",
|
||||
Deny => "deny",
|
||||
_ => "ask",
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsAuto(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), Auto, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsDeny(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), Deny, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool RequiresUserApproval(string? mode)
|
||||
{
|
||||
var normalized = NormalizeGlobalMode(mode);
|
||||
return !string.Equals(normalized, Auto, StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(normalized, Deny, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ public sealed class AppStateService
|
||||
Skills.Enabled = llm.EnableSkillSystem;
|
||||
Skills.FolderPath = llm.SkillsFolderPath ?? "";
|
||||
|
||||
Permissions.FilePermission = llm.FilePermission ?? "Ask";
|
||||
Permissions.FilePermission = PermissionModeCatalog.NormalizeGlobalMode(llm.FilePermission);
|
||||
Permissions.AgentDecisionLevel = llm.AgentDecisionLevel ?? "detailed";
|
||||
Permissions.PlanMode = llm.PlanMode ?? "off";
|
||||
Permissions.ToolOverrideCount = llm.ToolPermissions?.Count ?? 0;
|
||||
@@ -501,27 +501,31 @@ public sealed class AppStateService
|
||||
|
||||
public PermissionSummaryState GetPermissionSummary(ChatConversation? conversation = null)
|
||||
{
|
||||
var effective = conversation?.Permission;
|
||||
if (string.IsNullOrWhiteSpace(effective))
|
||||
effective = Permissions.FilePermission;
|
||||
var effective = PermissionModeCatalog.NormalizeGlobalMode(conversation?.Permission);
|
||||
var defaultMode = PermissionModeCatalog.NormalizeGlobalMode(Permissions.FilePermission);
|
||||
if (string.IsNullOrWhiteSpace(conversation?.Permission))
|
||||
effective = defaultMode;
|
||||
|
||||
var risk = string.Equals(effective, "Auto", StringComparison.OrdinalIgnoreCase)
|
||||
var risk = string.Equals(effective, PermissionModeCatalog.Auto, StringComparison.OrdinalIgnoreCase)
|
||||
? "high"
|
||||
: string.Equals(effective, "Deny", StringComparison.OrdinalIgnoreCase)
|
||||
: string.Equals(effective, PermissionModeCatalog.Deny, StringComparison.OrdinalIgnoreCase)
|
||||
? "locked"
|
||||
: string.Equals(effective, PermissionModeCatalog.Plan, StringComparison.OrdinalIgnoreCase)
|
||||
? "guarded"
|
||||
: "normal";
|
||||
|
||||
var description = effective switch
|
||||
{
|
||||
"Auto" => "파일 작업을 자동 허용합니다.",
|
||||
"Deny" => "파일 작업을 차단합니다.",
|
||||
"Plan" => "계획/승인 흐름을 우선 적용한 뒤 파일 작업을 진행합니다.",
|
||||
_ => "파일 작업 전마다 사용자 확인을 요청합니다.",
|
||||
};
|
||||
|
||||
return new PermissionSummaryState
|
||||
{
|
||||
EffectiveMode = effective ?? "Ask",
|
||||
DefaultMode = Permissions.FilePermission,
|
||||
EffectiveMode = effective,
|
||||
DefaultMode = defaultMode,
|
||||
OverrideCount = Permissions.ToolOverrideCount,
|
||||
RiskLevel = risk,
|
||||
Description = description,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services.Agent;
|
||||
|
||||
namespace AxCopilot.Services;
|
||||
|
||||
@@ -173,6 +174,15 @@ public class SettingsService
|
||||
|
||||
private void NormalizeRuntimeSettings()
|
||||
{
|
||||
_settings.Llm.FilePermission = PermissionModeCatalog.NormalizeGlobalMode(_settings.Llm.FilePermission);
|
||||
_settings.Llm.DefaultAgentPermission = PermissionModeCatalog.NormalizeGlobalMode(_settings.Llm.DefaultAgentPermission);
|
||||
if (_settings.Llm.ToolPermissions != null && _settings.Llm.ToolPermissions.Count > 0)
|
||||
{
|
||||
var keys = _settings.Llm.ToolPermissions.Keys.ToList();
|
||||
foreach (var key in keys)
|
||||
_settings.Llm.ToolPermissions[key] = PermissionModeCatalog.NormalizeToolOverride(_settings.Llm.ToolPermissions[key]);
|
||||
}
|
||||
|
||||
NormalizeLlmThresholds(_settings.Llm);
|
||||
}
|
||||
|
||||
@@ -262,3 +272,4 @@ public class SettingsService
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user