claw-code 동등 품질 4단계 연속 반영: Agentic 루프/상태복원/설정연동/릴리즈 게이트 정렬
Some checks failed
Release Gate / gate (push) Has been cancelled

- 도구 동등화: task/todo/tool-search + plan/worktree/team/cron 도구군 추가 및 ToolRegistry 등록\n- claw-code CamelCase 별칭 정규화 확장: EnterPlanMode/EnterWorktree/TeamCreate/CronCreate 등 -> 내부 snake_case 매핑\n- AgentLoop 런타임 강화: Code 탭 전용 도구 토글(CodeSettings) 반영, 비활성 도구 자동 차단\n- Worktree 상태 복원 연결: .ax/worktree_state.json 기반 루트 탐색/활성 worktree 복원 및 BuildContext 연동\n- 권한/플러그인 하드닝 기존 반영분 유지: target 기반 권한 판정 + internal 모드 플러그인 경로/manifest 검증\n- 설정 연동(UI): SettingsWindow Code 패널에 Plan/Worktree/Team/Cron 도구 on/off 토글 추가\n- 테스트 보강: AgentParityTools/AgentLoopE2E에 worktree 지속성, alias 정규화, 설정 차단 시나리오 추가\n- 검증 완료: dotnet build(경고0/오류0), ParityBenchmark 11/11, ReplayStability 12/12, 전체 371/371, release-gate 통과\n- 문서 동기화: AGENT_ROADMAP/NEXT_ROADMAP/CLAW_CODE_PARITY_PLAN 수치 및 기준 최신화
This commit is contained in:
2026-04-03 20:16:23 +09:00
parent 3b03b18f83
commit 2c047d062d
36 changed files with 1857 additions and 17 deletions

View File

@@ -1536,7 +1536,8 @@ public partial class AgentLoopService
IEnumerable<string>? disabledToolNames,
SkillRuntimeOverrides? runtimeOverrides)
{
var active = _tools.GetActiveTools(disabledToolNames);
var mergedDisabled = MergeDisabledTools(disabledToolNames);
var active = _tools.GetActiveTools(mergedDisabled);
if (runtimeOverrides == null || runtimeOverrides.AllowedToolNames.Count == 0)
return active;
@@ -1546,6 +1547,50 @@ public partial class AgentLoopService
.AsReadOnly();
}
private IEnumerable<string> MergeDisabledTools(IEnumerable<string>? disabledToolNames)
{
var disabled = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (disabledToolNames != null)
{
foreach (var name in disabledToolNames)
{
if (!string.IsNullOrWhiteSpace(name))
disabled.Add(name);
}
}
if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase))
return disabled;
var code = _settings.Settings.Llm.Code;
if (!code.EnablePlanModeTools)
{
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");
}
return disabled;
}
private static HashSet<string> ParseAllowedToolNames(string? raw)
{
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@@ -3510,6 +3555,25 @@ public partial class AgentLoopService
["task"] = "spawn_agent",
["sendmessage"] = "notify_tool",
["powershell"] = "process",
["toolsearch"] = "tool_search",
["todowrite"] = "todo_write",
["taskcreate"] = "task_create",
["taskget"] = "task_get",
["tasklist"] = "task_list",
["taskupdate"] = "task_update",
["taskstop"] = "task_stop",
["taskoutput"] = "task_output",
["enterplanmode"] = "enter_plan_mode",
["exitplanmode"] = "exit_plan_mode",
["enterworktree"] = "enter_worktree",
["exitworktree"] = "exit_worktree",
["teamcreate"] = "team_create",
["teamdelete"] = "team_delete",
["croncreate"] = "cron_create",
["crondelete"] = "cron_delete",
["cronlist"] = "cron_list",
["config"] = "project_rules",
["skill"] = "skill_manager",
};
private static string ResolveRequestedToolName(string requestedToolName, IReadOnlyCollection<string> activeToolNames)
@@ -3858,9 +3922,11 @@ public partial class AgentLoopService
private AgentContext BuildContext()
{
var llm = _settings.Settings.Llm;
var baseWorkFolder = llm.WorkFolder;
var runtimeWorkFolder = ResolveRuntimeWorkFolder(baseWorkFolder);
return new AgentContext
{
WorkFolder = llm.WorkFolder,
WorkFolder = runtimeWorkFolder,
Permission = llm.FilePermission,
BlockedPaths = llm.BlockedPaths,
BlockedExtensions = llm.BlockedExtensions,
@@ -3875,6 +3941,34 @@ public partial class AgentLoopService
};
}
private static string ResolveRuntimeWorkFolder(string? configuredRoot)
{
if (string.IsNullOrWhiteSpace(configuredRoot))
return "";
try
{
var root = Path.GetFullPath(configuredRoot);
if (!Directory.Exists(root))
return root;
var state = WorktreeStateStore.Load(root);
if (!string.IsNullOrWhiteSpace(state.Active))
{
var active = Path.GetFullPath(state.Active);
if (Directory.Exists(active)
&& active.StartsWith(root, StringComparison.OrdinalIgnoreCase))
return active;
}
return root;
}
catch
{
return configuredRoot ?? "";
}
}
private static string DescribeToolTarget(string toolName, JsonElement input, AgentContext context)
{
static string? TryReadString(JsonElement inputElement, params string[] names)
@@ -4049,7 +4143,7 @@ public partial class AgentLoopService
runId = _currentRunId,
tool = toolName,
target,
permission = context.GetEffectiveToolPermission(toolName)
permission = context.GetEffectiveToolPermission(toolName, target)
});
await RunPermissionLifecycleHooksAsync(
"__permission_request__",
@@ -4059,7 +4153,7 @@ public partial class AgentLoopService
messages,
success: true);
var effectivePerm = context.GetEffectiveToolPermission(toolName);
var effectivePerm = context.GetEffectiveToolPermission(toolName, target);
if (string.Equals(effectivePerm, "Ask", StringComparison.OrdinalIgnoreCase))
EmitEvent(AgentEventType.PermissionRequest, toolName, $"권한 확인 필요 · 대상: {target}");