diff --git a/README.md b/README.md index 71622f1..68b710b 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ public class MyHandler : IActionHandler ### v0.7.3 — AX Agent 권한 코어 재구성 + 입력 계층 정리 -업데이트: 2026-04-04 13:25 (KST) +업데이트: 2026-04-04 13:32 (KST) | 분류 | 내용 | |------|------| @@ -278,6 +278,7 @@ public class MyHandler : IActionHandler | slash 명령 카탈로그 분리 | `ChatWindow` 내부 대형 slash 사전을 `SlashCommandCatalog`로 분리해 입력 계층 결합도를 낮추고 유지보수 범위를 축소 | | slash 조회 API 전환 | 내장 slash 매칭/조회 경로를 `SlashCommandCatalog.MatchBuiltinCommands`/`TryGetEntry`로 통일 | | 권한 표시 카탈로그 분리 | 권한 모드 라벨/설명/아이콘/색을 `PermissionModePresentationCatalog`로 분리해 팝업 표면 기준을 단일화 | +| 탭별 설정 해석기 도입 | `AgentTabSettingsResolver`를 추가해 Cowork/Code 분기(검증 활성/Code 전용 도구 비활성)를 단일 경로로 정리 | | Slash palette 상태 분리 시작 | `ChatWindow`에 몰려 있던 slash 상태를 `SlashPaletteState`로 분리해 이후 Codex/Claude형 composer 개편 기반 마련 | | 런처 이미지 미리보기 추가 | `#` 클립보드 이미지 항목에서 `Shift+Enter`로 전용 미리보기 창을 열고, 줌·원본 해상도 확인·PNG/JPEG/BMP 저장·클립보드 복사를 지원 | | 검증 | `dotnet build` 경고 0 / 오류 0, `dotnet test` 436 passed / 0 failed | diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 9f6e6ca..3ed3bd6 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -3343,3 +3343,27 @@ else: ### 3) 품질 게이트 - dotnet build src/AxCopilot/AxCopilot.csproj 통과 (경고 0, 오류 0). - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj --no-build --filter "FullyQualifiedName~ChatWindowSlashPolicyTests|FullyQualifiedName~OperationModeReadinessTests" 통과 (43 passed, 0 failed). +## 2026-04-04 추가 진행 기록 (연속 실행 28차: 탭별 설정 해석기 도입) + +업데이트: 2026-04-04 13:32 (KST) + +### 1) 탭별 설정 해석 단일화 +- 신규 파일 AgentTabSettingsResolver 추가. +- ActiveTab 기준 공통 분기 제공: + - IsCodeTab, IsCoworkTab + - IsPostToolVerificationEnabled(activeTab, llm) + - EnumerateCodeTabDisabledTools(codeSettings) + +### 2) Agent loop 연동 +- AgentLoopService.MergeDisabledTools()에서 Code 탭 전용 도구 비활성 목록을 resolver로 계산하도록 전환. +- AgentLoopTransitions.Execution의 post-tool verification 판단에서 resolver 결과를 사용하도록 정리. + +### 3) 테스트 보강 +- AgentTabSettingsResolverTests 신규 추가: + - 탭 판별 + - Cowork/Code 검증 플래그 분기 + - Code 전용 비활성 도구 목록 계산 + +### 4) 품질 게이트 +- dotnet build src/AxCopilot/AxCopilot.csproj -p:UseSharedCompilation=false -nodeReuse:false 통과 (경고 0, 오류 0). +- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj --filter "FullyQualifiedName~AgentTabSettingsResolverTests|FullyQualifiedName~ChatWindowSlashPolicyTests|FullyQualifiedName~OperationModeReadinessTests" 통과 (49 passed, 0 failed). diff --git a/src/AxCopilot.Tests/Services/AgentTabSettingsResolverTests.cs b/src/AxCopilot.Tests/Services/AgentTabSettingsResolverTests.cs new file mode 100644 index 0000000..762449e --- /dev/null +++ b/src/AxCopilot.Tests/Services/AgentTabSettingsResolverTests.cs @@ -0,0 +1,56 @@ +using AxCopilot.Models; +using AxCopilot.Services.Agent; +using FluentAssertions; +using Xunit; + +namespace AxCopilot.Tests.Services; + +public class AgentTabSettingsResolverTests +{ + [Theory] + [InlineData("Code", true, false)] + [InlineData("Cowork", false, true)] + [InlineData("Chat", false, false)] + [InlineData(null, false, false)] + public void TabDetection_ShouldMatchExpected(string? tab, bool isCode, bool isCowork) + { + AgentTabSettingsResolver.IsCodeTab(tab).Should().Be(isCode); + AgentTabSettingsResolver.IsCoworkTab(tab).Should().Be(isCowork); + } + + [Fact] + public void IsPostToolVerificationEnabled_ShouldUseTabSpecificFlags() + { + var llm = new LlmSettings + { + EnableCoworkVerification = true, + Code = new CodeSettings + { + EnableCodeVerification = false, + } + }; + + AgentTabSettingsResolver.IsPostToolVerificationEnabled("Cowork", llm).Should().BeTrue(); + AgentTabSettingsResolver.IsPostToolVerificationEnabled("Code", llm).Should().BeFalse(); + AgentTabSettingsResolver.IsPostToolVerificationEnabled("Chat", llm).Should().BeFalse(); + } + + [Fact] + public void EnumerateCodeTabDisabledTools_ShouldReflectCodeSettings() + { + var code = new CodeSettings + { + EnablePlanModeTools = false, + EnableWorktreeTools = true, + EnableTeamTools = false, + EnableCronTools = false, + }; + + var disabled = AgentTabSettingsResolver.EnumerateCodeTabDisabledTools(code).ToList(); + + disabled.Should().Contain(["enter_plan_mode", "exit_plan_mode"]); + disabled.Should().Contain(["team_create", "team_delete"]); + disabled.Should().Contain(["cron_create", "cron_delete", "cron_list"]); + disabled.Should().NotContain(["enter_worktree", "exit_worktree"]); + } +} diff --git a/src/AxCopilot/Services/Agent/AgentLoopService.cs b/src/AxCopilot/Services/Agent/AgentLoopService.cs index cae7a85..8f668a4 100644 --- a/src/AxCopilot/Services/Agent/AgentLoopService.cs +++ b/src/AxCopilot/Services/Agent/AgentLoopService.cs @@ -1589,33 +1589,13 @@ public partial class AgentLoopService } } - if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase)) + if (!AgentTabSettingsResolver.IsCodeTab(ActiveTab)) return disabled; var code = _settings.Settings.Llm.Code; - if (!code.EnablePlanModeTools) + foreach (var toolName in AgentTabSettingsResolver.EnumerateCodeTabDisabledTools(code)) { - disabled.Add("enter_plan_mode"); - disabled.Add("exit_plan_mode"); - } - - if (!code.EnableWorktreeTools) - { - disabled.Add("enter_worktree"); - disabled.Add("exit_worktree"); - } - - if (!code.EnableTeamTools) - { - disabled.Add("team_create"); - disabled.Add("team_delete"); - } - - if (!code.EnableCronTools) - { - disabled.Add("cron_create"); - disabled.Add("cron_delete"); - disabled.Add("cron_list"); + disabled.Add(toolName); } return disabled; diff --git a/src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs b/src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs index a9d739d..1c73443 100644 --- a/src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs +++ b/src/AxCopilot/Services/Agent/AgentLoopTransitions.Execution.cs @@ -1399,12 +1399,13 @@ public partial class AgentLoopService if (!result.Success || !IsTerminalDocumentTool(call.ToolName) || toolCalls.Count != 1) return (false, false); + var verificationEnabled = AgentTabSettingsResolver.IsPostToolVerificationEnabled(ActiveTab, llm); var shouldVerify = ShouldRunPostToolVerification( ActiveTab, call.ToolName, result.Success, - llm.Code.EnableCodeVerification, - llm.EnableCoworkVerification); + verificationEnabled, + verificationEnabled); var consumedExtraIteration = false; if (shouldVerify) { @@ -1427,12 +1428,13 @@ public partial class AgentLoopService if (!result.Success) return false; + var verificationEnabled = AgentTabSettingsResolver.IsPostToolVerificationEnabled(ActiveTab, llm); var shouldVerify = ShouldRunPostToolVerification( ActiveTab, call.ToolName, result.Success, - llm.Code.EnableCodeVerification, - llm.EnableCoworkVerification); + verificationEnabled, + verificationEnabled); if (!shouldVerify) return false; diff --git a/src/AxCopilot/Services/Agent/AgentTabSettingsResolver.cs b/src/AxCopilot/Services/Agent/AgentTabSettingsResolver.cs new file mode 100644 index 0000000..3b50382 --- /dev/null +++ b/src/AxCopilot/Services/Agent/AgentTabSettingsResolver.cs @@ -0,0 +1,49 @@ +using AxCopilot.Models; + +namespace AxCopilot.Services.Agent; + +internal static class AgentTabSettingsResolver +{ + public static bool IsCodeTab(string? activeTab) + => string.Equals(activeTab, "Code", StringComparison.OrdinalIgnoreCase); + + public static bool IsCoworkTab(string? activeTab) + => string.Equals(activeTab, "Cowork", StringComparison.OrdinalIgnoreCase); + + public static bool IsPostToolVerificationEnabled(string? activeTab, LlmSettings llm) + { + if (IsCodeTab(activeTab)) + return llm.Code.EnableCodeVerification; + if (IsCoworkTab(activeTab)) + return llm.EnableCoworkVerification; + return false; + } + + public static IEnumerable EnumerateCodeTabDisabledTools(CodeSettings code) + { + if (!code.EnablePlanModeTools) + { + yield return "enter_plan_mode"; + yield return "exit_plan_mode"; + } + + if (!code.EnableWorktreeTools) + { + yield return "enter_worktree"; + yield return "exit_worktree"; + } + + if (!code.EnableTeamTools) + { + yield return "team_create"; + yield return "team_delete"; + } + + if (!code.EnableCronTools) + { + yield return "cron_create"; + yield return "cron_delete"; + yield return "cron_list"; + } + } +}