권한 코어를 claude-code 기준으로 재구성하고 slash palette 상태 분리를 시작\n\n- Default/AcceptEdits/Plan/BypassPermissions/DontAsk/Deny 권한 모드를 추가하고 기존 Ask/Auto 호환을 유지\n- deny 우선 패턴 규칙, allow/override, 글로벌 모드 순서의 권한 해석 체계를 정리\n- file_write/file_edit/file_manage와 process/build_run/test_loop/snippet_runner/spawn_agent 계열을 권한 클래스별로 분리\n- AcceptEdits는 파일 편집 도구 자동 허용, process 계열은 계속 확인하도록 조정\n- Plan은 쓰기 도구를 차단하고 읽기 중심 진행이 되도록 보강\n- BypassPermissions와 DontAsk는 권한 확인을 생략하는 경로로 정규화\n- AX Agent 권한 팝업, 상단 배너, slash 명령 결과를 새 권한 체계에 맞게 정리\n- /permissions, /allowed-tools, /sandbox-toggle 사용법과 상태 출력을 갱신\n- ChatWindow의 slash palette 상태를 전용 SlashPaletteState로 분리해 이후 composer 개편 기반을 마련\n- AppState, 설정 모델, 테스트를 새 권한 체계에 맞게 갱신\n- dotnet build 경고 0 / 오류 0, dotnet test 436 통과를 확인
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
This commit is contained in:
@@ -13,7 +13,7 @@ public class AppStateServiceTests
|
||||
public void LoadFromSettings_ReflectsPermissionAndMcpSummary()
|
||||
{
|
||||
var settings = new SettingsService();
|
||||
settings.Settings.Llm.FilePermission = "Auto";
|
||||
settings.Settings.Llm.FilePermission = "AcceptEdits";
|
||||
settings.Settings.Llm.AgentDecisionLevel = "normal";
|
||||
settings.Settings.Llm.PlanMode = "always";
|
||||
settings.Settings.Llm.ToolPermissions["process"] = "Deny";
|
||||
@@ -29,7 +29,7 @@ public class AppStateServiceTests
|
||||
|
||||
state.LoadFromSettings(settings);
|
||||
|
||||
state.Permissions.FilePermission.Should().Be("Auto");
|
||||
state.Permissions.FilePermission.Should().Be("AcceptEdits");
|
||||
state.Permissions.AgentDecisionLevel.Should().Be("normal");
|
||||
state.Permissions.PlanMode.Should().Be("always");
|
||||
state.Permissions.ToolOverrideCount.Should().Be(1);
|
||||
@@ -346,14 +346,14 @@ public class AppStateServiceTests
|
||||
{
|
||||
var state = new AppStateService();
|
||||
var settings = new SettingsService();
|
||||
settings.Settings.Llm.FilePermission = "Ask";
|
||||
settings.Settings.Llm.FilePermission = "Default";
|
||||
settings.Settings.Llm.ToolPermissions["process"] = "Deny";
|
||||
state.LoadFromSettings(settings);
|
||||
|
||||
var summary = state.GetPermissionSummary(new ChatConversation { Permission = "Auto" });
|
||||
var summary = state.GetPermissionSummary(new ChatConversation { Permission = "AcceptEdits" });
|
||||
|
||||
summary.EffectiveMode.Should().Be("Auto");
|
||||
summary.DefaultMode.Should().Be("Ask");
|
||||
summary.EffectiveMode.Should().Be("AcceptEdits");
|
||||
summary.DefaultMode.Should().Be("Default");
|
||||
summary.OverrideCount.Should().Be(1);
|
||||
summary.RiskLevel.Should().Be("high");
|
||||
summary.TopOverrides.Should().ContainSingle();
|
||||
|
||||
@@ -37,7 +37,7 @@ public class OperationModePolicyTests
|
||||
var context = new AgentContext
|
||||
{
|
||||
OperationMode = OperationModePolicy.InternalMode,
|
||||
Permission = "Auto"
|
||||
Permission = "AcceptEdits"
|
||||
};
|
||||
|
||||
var blocked = await context.CheckToolPermissionAsync("http_tool", "https://example.com");
|
||||
@@ -53,7 +53,7 @@ public class OperationModePolicyTests
|
||||
var context = new AgentContext
|
||||
{
|
||||
OperationMode = OperationModePolicy.ExternalMode,
|
||||
Permission = "Auto"
|
||||
Permission = "AcceptEdits"
|
||||
};
|
||||
|
||||
var allowed = await context.CheckToolPermissionAsync("http_tool", "https://example.com");
|
||||
@@ -65,18 +65,18 @@ public class OperationModePolicyTests
|
||||
{
|
||||
var context = new AgentContext
|
||||
{
|
||||
Permission = "Ask",
|
||||
Permission = "Default",
|
||||
ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["process"] = "deny",
|
||||
["process@git *"] = "auto",
|
||||
["*@*.md"] = "ask",
|
||||
["process@git *"] = "acceptedits",
|
||||
["*@*.md"] = "default",
|
||||
}
|
||||
};
|
||||
|
||||
context.GetEffectiveToolPermission("process", "git status").Should().Be("auto");
|
||||
context.GetEffectiveToolPermission("process", "powershell -NoProfile").Should().Be("deny");
|
||||
context.GetEffectiveToolPermission("file_read", @"E:\work\README.md").Should().Be("ask");
|
||||
context.GetEffectiveToolPermission("process", "git status").Should().Be("Default");
|
||||
context.GetEffectiveToolPermission("process", "powershell -NoProfile").Should().Be("Deny");
|
||||
context.GetEffectiveToolPermission("file_read", @"E:\work\README.md").Should().Be("AcceptEdits");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -86,10 +86,10 @@ public class OperationModePolicyTests
|
||||
var context = new AgentContext
|
||||
{
|
||||
OperationMode = OperationModePolicy.ExternalMode,
|
||||
Permission = "Ask",
|
||||
Permission = "Default",
|
||||
ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["process@git *"] = "auto",
|
||||
["process@git *"] = "bypassPermissions",
|
||||
},
|
||||
AskPermission = (_, _) =>
|
||||
{
|
||||
@@ -104,29 +104,48 @@ public class OperationModePolicyTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AgentContext_GetEffectiveToolPermission_DowngradesDangerousAutoToolWhenGlobalAuto()
|
||||
public void AgentContext_GetEffectiveToolPermission_AcceptEditsAllowsWriteButKeepsProcessPrompted()
|
||||
{
|
||||
var context = new AgentContext
|
||||
{
|
||||
Permission = "Auto",
|
||||
ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["process"] = "auto",
|
||||
}
|
||||
Permission = "AcceptEdits"
|
||||
};
|
||||
|
||||
context.GetEffectiveToolPermission("process", "git status").Should().Be("ask");
|
||||
context.GetEffectiveToolPermission("file_read", @"E:\work\README.md").Should().Be("Auto");
|
||||
context.GetEffectiveToolPermission("process", "git status").Should().Be("Default");
|
||||
context.GetEffectiveToolPermission("file_write", @"E:\work\out.txt").Should().Be("AcceptEdits");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AgentContext_CheckToolPermissionAsync_DangerousAutoToolRequiresPromptInGlobalAuto()
|
||||
public async Task AgentContext_CheckToolPermissionAsync_PlanModeBlocksWriteButAllowsRead()
|
||||
{
|
||||
var askCalled = false;
|
||||
var context = new AgentContext
|
||||
{
|
||||
OperationMode = OperationModePolicy.ExternalMode,
|
||||
Permission = "Auto",
|
||||
Permission = "Plan",
|
||||
AskPermission = (_, _) =>
|
||||
{
|
||||
askCalled = true;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
};
|
||||
|
||||
var writeAllowed = await context.CheckToolPermissionAsync("file_write", @"E:\work\out.txt");
|
||||
var readAllowed = await context.CheckToolPermissionAsync("file_read", @"E:\work\in.txt");
|
||||
|
||||
writeAllowed.Should().BeFalse();
|
||||
readAllowed.Should().BeTrue();
|
||||
askCalled.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AgentContext_CheckToolPermissionAsync_BypassPermissionsSkipsPrompt()
|
||||
{
|
||||
var askCalled = false;
|
||||
var context = new AgentContext
|
||||
{
|
||||
OperationMode = OperationModePolicy.ExternalMode,
|
||||
Permission = "BypassPermissions",
|
||||
AskPermission = (_, _) =>
|
||||
{
|
||||
askCalled = true;
|
||||
@@ -135,8 +154,8 @@ public class OperationModePolicyTests
|
||||
};
|
||||
|
||||
var allowed = await context.CheckToolPermissionAsync("process", "git status");
|
||||
allowed.Should().BeFalse();
|
||||
askCalled.Should().BeTrue();
|
||||
allowed.Should().BeTrue();
|
||||
askCalled.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -241,6 +241,10 @@ public class LauncherSettings
|
||||
/// <summary>독 바 마지막 위치 Y. -1이면 하단.</summary>
|
||||
[JsonPropertyName("dockBarTop")]
|
||||
public double DockBarTop { get; set; } = -1;
|
||||
|
||||
/// <summary>모니터별 독 바 위치. key=디바이스명, value=[left, top]</summary>
|
||||
[JsonPropertyName("monitorDockPositions")]
|
||||
public Dictionary<string, List<double>> MonitorDockPositions { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -591,14 +595,15 @@ public class LlmSettings
|
||||
|
||||
/// <summary>
|
||||
/// 파일 접근 권한 수준.
|
||||
/// Ask = 매번 확인 | Plan = 계획/승인 중심 | Auto = 자동 허용 | Deny = 차단
|
||||
/// Default = 매번 확인 | AcceptEdits = 파일 편집 자동 허용 | Plan = 계획/승인 중심
|
||||
/// BypassPermissions = 모든 확인 생략 | DontAsk = 권한 질문 없이 진행 | Deny = 읽기 전용
|
||||
/// </summary>
|
||||
[JsonPropertyName("filePermission")]
|
||||
public string FilePermission { get; set; } = "Ask";
|
||||
public string FilePermission { get; set; } = "Default";
|
||||
|
||||
/// <summary>Cowork/Code 탭의 기본 파일 접근 권한. 탭 전환 시 자동 적용.</summary>
|
||||
[JsonPropertyName("defaultAgentPermission")]
|
||||
public string DefaultAgentPermission { get; set; } = "Ask";
|
||||
public string DefaultAgentPermission { get; set; } = "Default";
|
||||
|
||||
// ── 서비스별 독립 설정 ──────────────────────────────────────
|
||||
[JsonPropertyName("ollamaEndpoint")] public string OllamaEndpoint { get; set; } = "http://localhost:11434";
|
||||
@@ -608,6 +613,7 @@ public class LlmSettings
|
||||
[JsonPropertyName("vllmEndpoint")] public string VllmEndpoint { get; set; } = "";
|
||||
[JsonPropertyName("vllmApiKey")] public string VllmApiKey { get; set; } = "";
|
||||
[JsonPropertyName("vllmModel")] public string VllmModel { get; set; } = "";
|
||||
[JsonPropertyName("vllmAllowInsecureTls")] public bool VllmAllowInsecureTls { get; set; } = false;
|
||||
|
||||
[JsonPropertyName("geminiApiKey")] public string GeminiApiKey { get; set; } = "";
|
||||
[JsonPropertyName("geminiModel")] public string GeminiModel { get; set; } = "gemini-2.5-flash";
|
||||
@@ -636,6 +642,14 @@ public class LlmSettings
|
||||
[JsonPropertyName("maxAgentIterations")]
|
||||
public int MaxAgentIterations { get; set; } = 25;
|
||||
|
||||
/// <summary>컨텍스트 사전 압축 활성화 여부.</summary>
|
||||
[JsonPropertyName("enableProactiveContextCompact")]
|
||||
public bool EnableProactiveContextCompact { get; set; } = true;
|
||||
|
||||
/// <summary>컨텍스트 압축 시작 임계치(%). 최대 토큰의 몇 %에서 압축 시작할지.</summary>
|
||||
[JsonPropertyName("contextCompactTriggerPercent")]
|
||||
public int ContextCompactTriggerPercent { get; set; } = 80;
|
||||
|
||||
/// <summary>도구 실패 시 최대 재시도 횟수 (Self-Reflection).</summary>
|
||||
[JsonPropertyName("maxRetryOnError")]
|
||||
public int MaxRetryOnError { get; set; } = 3;
|
||||
@@ -648,6 +662,10 @@ public class LlmSettings
|
||||
[JsonPropertyName("agentLogLevel")]
|
||||
public string AgentLogLevel { get; set; } = "simple";
|
||||
|
||||
/// <summary>AX Agent UI 표현 수준. rich | balanced | simple</summary>
|
||||
[JsonPropertyName("agentUiExpressionLevel")]
|
||||
public string AgentUiExpressionLevel { get; set; } = "balanced";
|
||||
|
||||
/// <summary>계획 diff 심각도 중간 기준: 변경 개수 임계값.</summary>
|
||||
[JsonPropertyName("planDiffSeverityMediumCount")]
|
||||
public int PlanDiffSeverityMediumCount { get; set; } = 2;
|
||||
@@ -699,9 +717,9 @@ public class LlmSettings
|
||||
[JsonPropertyName("lastActiveTab")]
|
||||
public string LastActiveTab { get; set; } = "Chat";
|
||||
|
||||
/// <summary>폴더 데이터 활용 모드. active(적극) | passive(소극) | none(미활용)</summary>
|
||||
/// <summary>폴더 데이터 활용 모드. none(미활용) | passive(소극) | active(적극)</summary>
|
||||
[JsonPropertyName("folderDataUsage")]
|
||||
public string FolderDataUsage { get; set; } = "active";
|
||||
public string FolderDataUsage { get; set; } = "none";
|
||||
|
||||
/// <summary>
|
||||
/// 에이전트 의사결정 수준. AI가 중요한 작업 전에 사용자 확인을 요청하는 빈도.
|
||||
@@ -795,6 +813,10 @@ public class LlmSettings
|
||||
[JsonPropertyName("enableChatRainbowGlow")]
|
||||
public bool EnableChatRainbowGlow { get; set; } = false;
|
||||
|
||||
/// <summary>AX Agent 전용 테마. system | light | dark</summary>
|
||||
[JsonPropertyName("agentTheme")]
|
||||
public string AgentTheme { get; set; } = "system";
|
||||
|
||||
// ─── 알림 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>에이전트 작업 완료 시 시스템 알림 표시 여부. (Cowork/Code 공통)</summary>
|
||||
@@ -866,7 +888,7 @@ public class LlmSettings
|
||||
public List<string> DisabledTools { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 도구별 실행 권한 오버라이드. 키: 도구 이름, 값: "ask" | "auto" | "deny".
|
||||
/// 도구별 실행 권한 오버라이드. 키: 도구 이름 또는 tool@pattern, 값: 권한 모드.
|
||||
/// 여기에 없는 도구는 전역 FilePermission 설정을 따릅니다.
|
||||
/// </summary>
|
||||
[JsonPropertyName("toolPermissions")]
|
||||
@@ -1125,6 +1147,10 @@ public class RegisteredModel
|
||||
[JsonPropertyName("apiKey")]
|
||||
public string ApiKey { get; set; } = "";
|
||||
|
||||
/// <summary>이 모델에 한해 TLS 인증서 검증을 생략합니다(vLLM 사내 인증서 예외 대응).</summary>
|
||||
[JsonPropertyName("allowInsecureTls")]
|
||||
public bool AllowInsecureTls { get; set; } = false;
|
||||
|
||||
// ── CP4D (IBM Cloud Pak for Data) 인증 ──────────────────────────────
|
||||
|
||||
/// <summary>인증 방식. bearer (기본) | cp4d</summary>
|
||||
|
||||
@@ -95,8 +95,22 @@ public class AgentContext
|
||||
private static readonly HashSet<string> DangerousAutoTools = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"process",
|
||||
"build_run",
|
||||
"spawn_agent",
|
||||
"snippet_runner",
|
||||
"test_loop",
|
||||
};
|
||||
private static readonly HashSet<string> WriteTools = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"file_write", "file_edit", "file_manage",
|
||||
"html_create", "markdown_create", "docx_create", "excel_create", "csv_create", "pptx_create",
|
||||
"chart_create", "script_create", "document_assemble", "format_convert", "template_render", "checkpoint",
|
||||
"todo_write", "skill_manager", "project_rule", "task_create", "task_update", "task_stop",
|
||||
"team_create", "team_delete", "cron_create", "cron_delete", "zip",
|
||||
};
|
||||
private static readonly HashSet<string> ProcessLikeTools = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"process", "build_run", "test_loop", "snippet_runner", "spawn_agent", "git_tool",
|
||||
};
|
||||
|
||||
private readonly object _permissionLock = new();
|
||||
@@ -104,10 +118,10 @@ public class AgentContext
|
||||
/// <summary>작업 폴더 경로.</summary>
|
||||
public string WorkFolder { get; set; } = "";
|
||||
|
||||
/// <summary>파일 접근 권한. Ask | Plan | Auto | Deny</summary>
|
||||
public string Permission { get; init; } = "Ask";
|
||||
/// <summary>파일 접근 권한. Default | AcceptEdits | Plan | BypassPermissions | DontAsk | Deny</summary>
|
||||
public string Permission { get; init; } = "Default";
|
||||
|
||||
/// <summary>도구별 권한 오버라이드. 키: 도구명, 값: "ask" | "auto" | "deny".</summary>
|
||||
/// <summary>도구별 권한 오버라이드. 키: 도구명 또는 tool@pattern, 값: 권한 모드.</summary>
|
||||
public Dictionary<string, string> ToolPermissions { get; init; } = new();
|
||||
|
||||
/// <summary>차단 경로 패턴 목록.</summary>
|
||||
@@ -201,22 +215,22 @@ public class AgentContext
|
||||
var normalizedToolName = toolName.Trim();
|
||||
|
||||
if (TryResolvePatternPermission(toolName, target, out var patternPermission))
|
||||
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(patternPermission));
|
||||
return ResolveModeForTool(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(patternPermission));
|
||||
|
||||
if (ToolPermissions.TryGetValue(toolName, out var toolPerm) &&
|
||||
!string.IsNullOrWhiteSpace(toolPerm))
|
||||
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(toolPerm));
|
||||
return ResolveModeForTool(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(toolPerm));
|
||||
if (ToolPermissions.TryGetValue("*", out var wildcardPerm) &&
|
||||
!string.IsNullOrWhiteSpace(wildcardPerm))
|
||||
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(wildcardPerm));
|
||||
return ResolveModeForTool(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(wildcardPerm));
|
||||
if (ToolPermissions.TryGetValue("default", out var defaultPerm) &&
|
||||
!string.IsNullOrWhiteSpace(defaultPerm))
|
||||
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(defaultPerm));
|
||||
return ResolveModeForTool(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(defaultPerm));
|
||||
|
||||
var fallback = SensitiveTools.Contains(toolName)
|
||||
? PermissionModeCatalog.NormalizeGlobalMode(Permission)
|
||||
: PermissionModeCatalog.Auto;
|
||||
return ApplyDangerousAutoGuard(normalizedToolName, fallback);
|
||||
: PermissionModeCatalog.AcceptEdits;
|
||||
return ResolveModeForTool(normalizedToolName, fallback);
|
||||
}
|
||||
|
||||
public async Task<bool> CheckToolPermissionAsync(string toolName, string target)
|
||||
@@ -256,6 +270,30 @@ public class AgentContext
|
||||
var normalizedTool = toolName.Trim();
|
||||
var normalizedTarget = target.Trim();
|
||||
|
||||
foreach (var kv in ToolPermissions)
|
||||
{
|
||||
if (TryParsePatternRule(kv.Key, out var ruleTool, out var rulePattern)
|
||||
&& string.Equals(ruleTool, normalizedTool, StringComparison.OrdinalIgnoreCase)
|
||||
&& WildcardMatch(normalizedTarget, rulePattern)
|
||||
&& PermissionModeCatalog.IsDeny(kv.Value))
|
||||
{
|
||||
permission = kv.Value.Trim();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in ToolPermissions)
|
||||
{
|
||||
if (TryParsePatternRule(kv.Key, out var ruleTool, out var rulePattern)
|
||||
&& string.Equals(ruleTool, "*", StringComparison.Ordinal)
|
||||
&& WildcardMatch(normalizedTarget, rulePattern)
|
||||
&& PermissionModeCatalog.IsDeny(kv.Value))
|
||||
{
|
||||
permission = kv.Value.Trim();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in ToolPermissions)
|
||||
{
|
||||
if (TryParsePatternRule(kv.Key, out var ruleTool, out var rulePattern)
|
||||
@@ -308,15 +346,63 @@ public class AgentContext
|
||||
return Regex.IsMatch(input, regex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
private string ResolveModeForTool(string toolName, string mode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toolName))
|
||||
return mode;
|
||||
|
||||
var normalizedMode = PermissionModeCatalog.NormalizeGlobalMode(mode);
|
||||
if (PermissionModeCatalog.IsDeny(normalizedMode))
|
||||
return PermissionModeCatalog.Deny;
|
||||
|
||||
if (PermissionModeCatalog.IsBypassPermissions(normalizedMode) || PermissionModeCatalog.IsDontAsk(normalizedMode))
|
||||
return normalizedMode;
|
||||
|
||||
if (PermissionModeCatalog.IsPlan(normalizedMode))
|
||||
{
|
||||
if (IsWriteTool(toolName))
|
||||
return PermissionModeCatalog.Deny;
|
||||
|
||||
return IsProcessLikeTool(toolName)
|
||||
? PermissionModeCatalog.Default
|
||||
: PermissionModeCatalog.AcceptEdits;
|
||||
}
|
||||
|
||||
if (PermissionModeCatalog.IsAcceptEdits(normalizedMode))
|
||||
{
|
||||
if (IsWriteTool(toolName))
|
||||
return PermissionModeCatalog.AcceptEdits;
|
||||
|
||||
return IsProcessLikeTool(toolName)
|
||||
? PermissionModeCatalog.Default
|
||||
: ApplyDangerousAutoGuard(toolName, PermissionModeCatalog.AcceptEdits);
|
||||
}
|
||||
|
||||
if (PermissionModeCatalog.IsDefault(normalizedMode))
|
||||
{
|
||||
if (!SensitiveTools.Contains(toolName))
|
||||
return PermissionModeCatalog.AcceptEdits;
|
||||
|
||||
return ApplyDangerousAutoGuard(toolName, PermissionModeCatalog.Default);
|
||||
}
|
||||
|
||||
return ApplyDangerousAutoGuard(toolName, normalizedMode);
|
||||
}
|
||||
|
||||
private static bool IsWriteTool(string toolName) => WriteTools.Contains(toolName);
|
||||
|
||||
private static bool IsProcessLikeTool(string toolName) => ProcessLikeTools.Contains(toolName);
|
||||
|
||||
private string ApplyDangerousAutoGuard(string toolName, string permission)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toolName))
|
||||
return permission;
|
||||
|
||||
if (PermissionModeCatalog.IsAuto(permission)
|
||||
&& PermissionModeCatalog.IsAuto(Permission)
|
||||
&& !PermissionModeCatalog.IsBypassPermissions(permission)
|
||||
&& !PermissionModeCatalog.IsDontAsk(permission)
|
||||
&& DangerousAutoTools.Contains(toolName))
|
||||
return "ask";
|
||||
return PermissionModeCatalog.Default;
|
||||
|
||||
return permission;
|
||||
}
|
||||
|
||||
@@ -6,16 +6,22 @@
|
||||
/// </summary>
|
||||
public static class PermissionModeCatalog
|
||||
{
|
||||
public const string Ask = "Ask";
|
||||
public const string Default = "Default";
|
||||
public const string Ask = Default;
|
||||
public const string AcceptEdits = "AcceptEdits";
|
||||
public const string Plan = "Plan";
|
||||
public const string Auto = "Auto";
|
||||
public const string BypassPermissions = "BypassPermissions";
|
||||
public const string DontAsk = "DontAsk";
|
||||
public const string Auto = AcceptEdits;
|
||||
public const string Deny = "Deny";
|
||||
|
||||
public static readonly IReadOnlyList<string> UserSelectableModes = new[]
|
||||
{
|
||||
Ask,
|
||||
Default,
|
||||
AcceptEdits,
|
||||
Plan,
|
||||
Auto,
|
||||
BypassPermissions,
|
||||
DontAsk,
|
||||
Deny,
|
||||
};
|
||||
|
||||
@@ -26,15 +32,23 @@ public static class PermissionModeCatalog
|
||||
public static string NormalizeGlobalMode(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return Ask;
|
||||
return Default;
|
||||
|
||||
return value.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"default" => Default,
|
||||
"ask" => Ask,
|
||||
"plan" => Plan,
|
||||
"acceptedits" => AcceptEdits,
|
||||
"accept" => AcceptEdits,
|
||||
"auto" => Auto,
|
||||
"plan" => Plan,
|
||||
"bypasspermissions" => BypassPermissions,
|
||||
"bypass" => BypassPermissions,
|
||||
"dontask" => DontAsk,
|
||||
"don't ask" => DontAsk,
|
||||
"allow" => AcceptEdits,
|
||||
"deny" => Deny,
|
||||
_ => Ask,
|
||||
_ => Default,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,14 +61,32 @@ public static class PermissionModeCatalog
|
||||
var mode = NormalizeGlobalMode(value);
|
||||
return mode switch
|
||||
{
|
||||
Auto => "auto",
|
||||
Deny => "deny",
|
||||
_ => "ask",
|
||||
AcceptEdits => AcceptEdits,
|
||||
Plan => Plan,
|
||||
BypassPermissions => BypassPermissions,
|
||||
DontAsk => DontAsk,
|
||||
Deny => Deny,
|
||||
_ => Default,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsAuto(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), Auto, StringComparison.OrdinalIgnoreCase);
|
||||
IsAcceptEdits(mode) || IsBypassPermissions(mode) || IsDontAsk(mode);
|
||||
|
||||
public static bool IsDefault(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), Default, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsAcceptEdits(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), AcceptEdits, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsPlan(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), Plan, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsBypassPermissions(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), BypassPermissions, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsDontAsk(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), DontAsk, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsDeny(string? mode) =>
|
||||
string.Equals(NormalizeGlobalMode(mode), Deny, StringComparison.OrdinalIgnoreCase);
|
||||
@@ -62,7 +94,24 @@ public static class PermissionModeCatalog
|
||||
public static bool RequiresUserApproval(string? mode)
|
||||
{
|
||||
var normalized = NormalizeGlobalMode(mode);
|
||||
return !string.Equals(normalized, Auto, StringComparison.OrdinalIgnoreCase)
|
||||
return !IsAcceptEdits(normalized)
|
||||
&& !IsBypassPermissions(normalized)
|
||||
&& !IsDontAsk(normalized)
|
||||
&& !string.Equals(normalized, Deny, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string ToDisplayLabel(string? mode)
|
||||
{
|
||||
var normalized = NormalizeGlobalMode(mode);
|
||||
return normalized switch
|
||||
{
|
||||
Default => "소극 활용",
|
||||
AcceptEdits => "적극 활용",
|
||||
Plan => "Plan",
|
||||
BypassPermissions => "Bypass",
|
||||
DontAsk => "DontAsk",
|
||||
Deny => "활용하지 않음",
|
||||
_ => normalized,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class AppStateService
|
||||
|
||||
public sealed class PermissionPolicyState
|
||||
{
|
||||
public string FilePermission { get; set; } = "Ask";
|
||||
public string FilePermission { get; set; } = "Default";
|
||||
public string AgentDecisionLevel { get; set; } = "detailed";
|
||||
public string PlanMode { get; set; } = "off";
|
||||
public int ToolOverrideCount { get; set; }
|
||||
@@ -506,7 +506,9 @@ public sealed class AppStateService
|
||||
if (string.IsNullOrWhiteSpace(conversation?.Permission))
|
||||
effective = defaultMode;
|
||||
|
||||
var risk = string.Equals(effective, PermissionModeCatalog.Auto, StringComparison.OrdinalIgnoreCase)
|
||||
var risk = PermissionModeCatalog.IsBypassPermissions(effective) || PermissionModeCatalog.IsDontAsk(effective)
|
||||
? "critical"
|
||||
: PermissionModeCatalog.IsAcceptEdits(effective)
|
||||
? "high"
|
||||
: string.Equals(effective, PermissionModeCatalog.Deny, StringComparison.OrdinalIgnoreCase)
|
||||
? "locked"
|
||||
@@ -516,9 +518,11 @@ public sealed class AppStateService
|
||||
|
||||
var description = effective switch
|
||||
{
|
||||
"Auto" => "파일 작업을 자동 허용합니다.",
|
||||
"Deny" => "파일 작업을 차단합니다.",
|
||||
"AcceptEdits" => "파일 편집 도구는 자동 허용하고 명령 실행은 계속 확인합니다.",
|
||||
"Deny" => "파일 읽기만 허용하고 생성/수정/삭제는 차단합니다.",
|
||||
"Plan" => "계획/승인 흐름을 우선 적용한 뒤 파일 작업을 진행합니다.",
|
||||
"BypassPermissions" => "모든 권한 확인을 생략합니다. 주의해서 사용해야 합니다.",
|
||||
"DontAsk" => "권한 질문 없이 진행합니다. 자동 실행 범위를 반드시 점검해야 합니다.",
|
||||
_ => "파일 작업 전마다 사용자 확인을 요청합니다.",
|
||||
};
|
||||
|
||||
|
||||
383
src/AxCopilot/Views/AgentSettingsWindow.xaml.cs
Normal file
383
src/AxCopilot/Views/AgentSettingsWindow.xaml.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Services.Agent;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
public partial class AgentSettingsWindow : Window
|
||||
{
|
||||
private const string UnifiedAdminPassword = "axgo123!";
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
private readonly LlmSettings _llm;
|
||||
private string _permissionMode = PermissionModeCatalog.Ask;
|
||||
private string _planMode = "off";
|
||||
private string _reasoningMode = "detailed";
|
||||
private string _folderDataUsage = "active";
|
||||
private string _operationMode = OperationModePolicy.InternalMode;
|
||||
|
||||
public AgentSettingsWindow(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_llm = _settings.Settings.Llm;
|
||||
InitializeComponent();
|
||||
LoadFromSettings();
|
||||
}
|
||||
|
||||
private void LoadFromSettings()
|
||||
{
|
||||
ModelInput.Text = _llm.Model ?? "";
|
||||
_permissionMode = PermissionModeCatalog.NormalizeGlobalMode(_llm.FilePermission);
|
||||
_planMode = string.IsNullOrWhiteSpace(_llm.PlanMode) ? "off" : _llm.PlanMode;
|
||||
_reasoningMode = string.IsNullOrWhiteSpace(_llm.AgentDecisionLevel) ? "detailed" : _llm.AgentDecisionLevel;
|
||||
_folderDataUsage = string.IsNullOrWhiteSpace(_llm.FolderDataUsage) ? "active" : _llm.FolderDataUsage;
|
||||
_operationMode = OperationModePolicy.Normalize(_settings.Settings.OperationMode);
|
||||
|
||||
ChkVllmAllowInsecureTls.IsChecked = _llm.VllmAllowInsecureTls;
|
||||
ChkEnableProactiveCompact.IsChecked = _llm.EnableProactiveContextCompact;
|
||||
TxtContextCompactTriggerPercent.Text = Math.Clamp(_llm.ContextCompactTriggerPercent, 10, 95).ToString();
|
||||
TxtMaxContextTokens.Text = Math.Max(1024, _llm.MaxContextTokens).ToString();
|
||||
TxtMaxRetryOnError.Text = Math.Clamp(_llm.MaxRetryOnError, 0, 10).ToString();
|
||||
|
||||
ChkEnableSkillSystem.IsChecked = _llm.EnableSkillSystem;
|
||||
ChkEnableToolHooks.IsChecked = _llm.EnableToolHooks;
|
||||
ChkEnableHookInputMutation.IsChecked = _llm.EnableHookInputMutation;
|
||||
ChkEnableHookPermissionUpdate.IsChecked = _llm.EnableHookPermissionUpdate;
|
||||
ChkEnableCoworkVerification.IsChecked = _llm.EnableCoworkVerification;
|
||||
ChkEnableCodeVerification.IsChecked = _llm.Code.EnableCodeVerification;
|
||||
ChkEnableParallelTools.IsChecked = _llm.EnableParallelTools;
|
||||
|
||||
RefreshServiceCards();
|
||||
RefreshThemeCards();
|
||||
RefreshModeLabels();
|
||||
BuildModelChips();
|
||||
}
|
||||
|
||||
private void RefreshThemeCards()
|
||||
{
|
||||
var selected = (_llm.AgentTheme ?? "system").ToLowerInvariant();
|
||||
SetCardSelection(ThemeSystemCard, selected == "system");
|
||||
SetCardSelection(ThemeLightCard, selected == "light");
|
||||
SetCardSelection(ThemeDarkCard, selected == "dark");
|
||||
}
|
||||
|
||||
private void RefreshServiceCards()
|
||||
{
|
||||
var service = (_llm.Service ?? "ollama").ToLowerInvariant();
|
||||
SetCardSelection(SvcOllamaCard, service == "ollama");
|
||||
SetCardSelection(SvcVllmCard, service == "vllm");
|
||||
SetCardSelection(SvcGeminiCard, service == "gemini");
|
||||
SetCardSelection(SvcClaudeCard, service is "claude" or "sigmoid");
|
||||
}
|
||||
|
||||
private void RefreshModeLabels()
|
||||
{
|
||||
BtnOperationMode.Content = BuildOperationModeLabel(_operationMode);
|
||||
BtnPermissionMode.Content = PermissionModeCatalog.ToDisplayLabel(_permissionMode);
|
||||
BtnPlanMode.Content = _planMode;
|
||||
BtnReasoningMode.Content = _reasoningMode;
|
||||
BtnFolderDataUsage.Content = _folderDataUsage;
|
||||
AdvancedPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private static string BuildOperationModeLabel(string mode)
|
||||
{
|
||||
return OperationModePolicy.Normalize(mode) == OperationModePolicy.ExternalMode
|
||||
? "사외 모드"
|
||||
: "사내 모드";
|
||||
}
|
||||
|
||||
private void SetCardSelection(Border border, bool selected)
|
||||
{
|
||||
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
|
||||
var normal = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
border.BorderBrush = selected ? accent : normal;
|
||||
border.Background = selected
|
||||
? (TryFindResource("HintBackground") as Brush ?? Brushes.Transparent)
|
||||
: Brushes.Transparent;
|
||||
}
|
||||
|
||||
private void BuildModelChips()
|
||||
{
|
||||
ModelChipPanel.Children.Clear();
|
||||
var models = GetModelCandidates(_llm.Service);
|
||||
foreach (var model in models)
|
||||
{
|
||||
var captured = model;
|
||||
var border = new Border
|
||||
{
|
||||
Cursor = Cursors.Hand,
|
||||
CornerRadius = new CornerRadius(10),
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
|
||||
Background = Brushes.Transparent,
|
||||
Padding = new Thickness(10, 6, 10, 6),
|
||||
Margin = new Thickness(0, 0, 8, 8),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = model,
|
||||
FontSize = 11,
|
||||
Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.White,
|
||||
},
|
||||
};
|
||||
border.MouseLeftButtonUp += (_, _) => ModelInput.Text = captured;
|
||||
ModelChipPanel.Children.Add(border);
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> GetModelCandidates(string? service)
|
||||
{
|
||||
var key = (service ?? "ollama").ToLowerInvariant();
|
||||
var result = new List<string>();
|
||||
|
||||
foreach (var m in _llm.RegisteredModels)
|
||||
{
|
||||
if (!string.Equals(m.Service, key, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (!string.IsNullOrWhiteSpace(m.Alias) && !result.Contains(m.Alias))
|
||||
result.Add(m.Alias);
|
||||
}
|
||||
|
||||
void AddIf(string? value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value) && !result.Contains(value))
|
||||
result.Add(value);
|
||||
}
|
||||
|
||||
if (key == "ollama") AddIf(_llm.OllamaModel);
|
||||
else if (key == "vllm") AddIf(_llm.VllmModel);
|
||||
else if (key == "gemini") AddIf(_llm.GeminiModel);
|
||||
else AddIf(_llm.ClaudeModel);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetService(string service)
|
||||
{
|
||||
_llm.Service = service;
|
||||
RefreshServiceCards();
|
||||
BuildModelChips();
|
||||
}
|
||||
|
||||
private void SetTheme(string theme)
|
||||
{
|
||||
_llm.AgentTheme = theme;
|
||||
RefreshThemeCards();
|
||||
}
|
||||
|
||||
private static string CycleOperationMode(string current)
|
||||
{
|
||||
return OperationModePolicy.Normalize(current) == OperationModePolicy.ExternalMode
|
||||
? OperationModePolicy.InternalMode
|
||||
: OperationModePolicy.ExternalMode;
|
||||
}
|
||||
|
||||
private bool PromptPasswordDialog(string title, string header, string message)
|
||||
{
|
||||
var bgBrush = TryFindResource("LauncherBackground") as Brush ?? Brushes.White;
|
||||
var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
|
||||
var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.WhiteSmoke;
|
||||
|
||||
var dlg = new Window
|
||||
{
|
||||
Title = title,
|
||||
Width = 340,
|
||||
SizeToContent = SizeToContent.Height,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
Owner = this,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
WindowStyle = WindowStyle.None,
|
||||
AllowsTransparency = true,
|
||||
Background = Brushes.Transparent,
|
||||
ShowInTaskbar = false,
|
||||
};
|
||||
|
||||
var border = new Border
|
||||
{
|
||||
Background = bgBrush,
|
||||
CornerRadius = new CornerRadius(12),
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(20),
|
||||
};
|
||||
|
||||
var stack = new StackPanel();
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = header,
|
||||
FontSize = 15,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = fgBrush,
|
||||
Margin = new Thickness(0, 0, 0, 12),
|
||||
});
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = message,
|
||||
FontSize = 12,
|
||||
Foreground = subFgBrush,
|
||||
Margin = new Thickness(0, 0, 0, 6),
|
||||
});
|
||||
|
||||
var pwBox = new PasswordBox
|
||||
{
|
||||
FontSize = 14,
|
||||
Padding = new Thickness(8, 6, 8, 6),
|
||||
Background = itemBg,
|
||||
Foreground = fgBrush,
|
||||
BorderBrush = borderBrush,
|
||||
PasswordChar = '*',
|
||||
};
|
||||
stack.Children.Add(pwBox);
|
||||
|
||||
var btnRow = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 16, 0, 0),
|
||||
};
|
||||
|
||||
var cancelBtn = new Button { Content = "취소", Padding = new Thickness(16, 6, 16, 6), Margin = new Thickness(0, 0, 8, 0) };
|
||||
cancelBtn.Click += (_, _) => dlg.DialogResult = false;
|
||||
btnRow.Children.Add(cancelBtn);
|
||||
|
||||
var okBtn = new Button { Content = "확인", Padding = new Thickness(16, 6, 16, 6), IsDefault = true };
|
||||
okBtn.Click += (_, _) =>
|
||||
{
|
||||
if (pwBox.Password == UnifiedAdminPassword)
|
||||
dlg.DialogResult = true;
|
||||
else
|
||||
{
|
||||
pwBox.Clear();
|
||||
pwBox.Focus();
|
||||
}
|
||||
};
|
||||
btnRow.Children.Add(okBtn);
|
||||
stack.Children.Add(btnRow);
|
||||
|
||||
border.Child = stack;
|
||||
dlg.Content = border;
|
||||
dlg.Loaded += (_, _) => pwBox.Focus();
|
||||
return dlg.ShowDialog() == true;
|
||||
}
|
||||
|
||||
private void ThemeSystemCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetTheme("system");
|
||||
private void ThemeLightCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetTheme("light");
|
||||
private void ThemeDarkCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetTheme("dark");
|
||||
|
||||
private void SvcOllamaCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("ollama");
|
||||
private void SvcVllmCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("vllm");
|
||||
private void SvcGeminiCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("gemini");
|
||||
private void SvcClaudeCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("claude");
|
||||
|
||||
private void BtnOperationMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var next = CycleOperationMode(_operationMode);
|
||||
if (!PromptPasswordDialog(
|
||||
"운영 모드 변경",
|
||||
"사내/사외 모드 변경",
|
||||
"비밀번호를 입력하세요:"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_operationMode = next;
|
||||
RefreshModeLabels();
|
||||
}
|
||||
|
||||
private void BtnPermissionMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_permissionMode = PermissionModeCatalog.NormalizeGlobalMode(_permissionMode) switch
|
||||
{
|
||||
PermissionModeCatalog.Deny => PermissionModeCatalog.Default,
|
||||
PermissionModeCatalog.Default => PermissionModeCatalog.AcceptEdits,
|
||||
PermissionModeCatalog.AcceptEdits => PermissionModeCatalog.Plan,
|
||||
PermissionModeCatalog.Plan => PermissionModeCatalog.BypassPermissions,
|
||||
PermissionModeCatalog.BypassPermissions => PermissionModeCatalog.DontAsk,
|
||||
_ => PermissionModeCatalog.Deny,
|
||||
};
|
||||
RefreshModeLabels();
|
||||
}
|
||||
|
||||
private void BtnPlanMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_planMode = _planMode switch
|
||||
{
|
||||
"off" => "auto",
|
||||
"auto" => "always",
|
||||
_ => "off",
|
||||
};
|
||||
RefreshModeLabels();
|
||||
}
|
||||
|
||||
private void BtnReasoningMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_reasoningMode = _reasoningMode switch
|
||||
{
|
||||
"minimal" => "normal",
|
||||
"normal" => "detailed",
|
||||
_ => "minimal",
|
||||
};
|
||||
RefreshModeLabels();
|
||||
}
|
||||
|
||||
private void BtnFolderDataUsage_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_folderDataUsage = _folderDataUsage switch
|
||||
{
|
||||
"none" => "passive",
|
||||
"passive" => "active",
|
||||
_ => "none",
|
||||
};
|
||||
RefreshModeLabels();
|
||||
}
|
||||
|
||||
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_llm.Model = ModelInput.Text.Trim();
|
||||
_llm.FilePermission = _permissionMode;
|
||||
_llm.PlanMode = _planMode;
|
||||
_llm.AgentDecisionLevel = _reasoningMode;
|
||||
_llm.FolderDataUsage = _folderDataUsage;
|
||||
_llm.AgentUiExpressionLevel = "rich";
|
||||
|
||||
_llm.VllmAllowInsecureTls = ChkVllmAllowInsecureTls.IsChecked == true;
|
||||
_llm.EnableProactiveContextCompact = ChkEnableProactiveCompact.IsChecked == true;
|
||||
_llm.ContextCompactTriggerPercent = ParseInt(TxtContextCompactTriggerPercent.Text, 80, 10, 95);
|
||||
_llm.MaxContextTokens = ParseInt(TxtMaxContextTokens.Text, 4096, 1024, 200000);
|
||||
_llm.MaxRetryOnError = ParseInt(TxtMaxRetryOnError.Text, 3, 0, 10);
|
||||
|
||||
_llm.EnableSkillSystem = ChkEnableSkillSystem.IsChecked == true;
|
||||
_llm.EnableToolHooks = ChkEnableToolHooks.IsChecked == true;
|
||||
_llm.EnableHookInputMutation = ChkEnableHookInputMutation.IsChecked == true;
|
||||
_llm.EnableHookPermissionUpdate = ChkEnableHookPermissionUpdate.IsChecked == true;
|
||||
_llm.EnableCoworkVerification = ChkEnableCoworkVerification.IsChecked == true;
|
||||
_llm.Code.EnableCodeVerification = ChkEnableCodeVerification.IsChecked == true;
|
||||
_llm.EnableParallelTools = ChkEnableParallelTools.IsChecked == true;
|
||||
|
||||
_settings.Settings.OperationMode = OperationModePolicy.Normalize(_operationMode);
|
||||
_settings.Save();
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void BtnOpenFullSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (System.Windows.Application.Current is App app)
|
||||
app.OpenSettingsFromChat();
|
||||
}
|
||||
|
||||
private void BtnClose_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => Close();
|
||||
|
||||
private static int ParseInt(string? text, int fallback, int min, int max)
|
||||
{
|
||||
if (!int.TryParse(text, out var value))
|
||||
value = fallback;
|
||||
return Math.Clamp(value, min, max);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
23
src/AxCopilot/Views/SlashPaletteState.cs
Normal file
23
src/AxCopilot/Views/SlashPaletteState.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
internal sealed class SlashPaletteState
|
||||
{
|
||||
public List<(string Cmd, string Label, bool IsSkill)> Matches { get; set; } = [];
|
||||
|
||||
public int SelectedIndex { get; set; } = -1;
|
||||
|
||||
public string? ActiveCommand { get; set; }
|
||||
|
||||
public void ResetMatches(List<(string Cmd, string Label, bool IsSkill)> matches)
|
||||
{
|
||||
Matches = matches;
|
||||
SelectedIndex = -1;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Matches = [];
|
||||
SelectedIndex = -1;
|
||||
ActiveCommand = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user