변경 목적: - AX Agent의 도구 이름, 내부 설정, 스킬 정책, 실행 루프 사이의 불일치를 줄이고 전체 동작 품질을 높인다. - claw-code 수준의 일관된 동작 품질을 참고하되 AX 구조에 맞는 고유한 카탈로그·정규화 레이어로 재구성한다. 핵심 수정사항: - 도구 canonical id, legacy alias, 탭 노출, 설정 카테고리, read-only 분류를 중앙 카탈로그로 통합했다. - ToolRegistry, AgentLoopService, 병렬 실행 분류, 권한 처리, 훅 처리, 스킬 allowed-tools 해석이 같은 이름 체계를 사용하도록 정리했다. - Agent 설정/일반 설정/도움말의 도구 카드와 훅 편집기, 스킬 설명을 현재 런타임 구조에 맞게 갱신했다. - 컨텍스트 압축, intent gate, spawn agents, session learning, model prompt adapter, workspace context 관련 변경과 테스트 추가를 함께 반영했다. - 문서 이력과 비교/로드맵 문서를 최신 상태로 갱신했다. 검증 결과: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_toolcat\ -p:IntermediateOutputPath=obj\verify_toolcat\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentToolCatalogTests -p:OutputPath=bin\verify_toolcat_tests\ -p:IntermediateOutputPath=obj\verify_toolcat_tests\ : 통과 8
138 lines
4.8 KiB
C#
138 lines
4.8 KiB
C#
using System.Text.Json;
|
|
using AxCopilot.Services.Agent;
|
|
using FluentAssertions;
|
|
using Xunit;
|
|
|
|
namespace AxCopilot.Tests.Services;
|
|
|
|
public class SpawnAgentsToolTests
|
|
{
|
|
// ═══════════════════════════════════════════
|
|
// 도구 메타데이터
|
|
// ═══════════════════════════════════════════
|
|
|
|
[Fact]
|
|
public void Name_IsSpawnAgents()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
tool.Name.Should().Be("spawn_agents");
|
|
}
|
|
|
|
[Fact]
|
|
public void Description_IsNonEmpty()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
tool.Description.Should().NotBeNullOrWhiteSpace();
|
|
}
|
|
|
|
[Fact]
|
|
public void Parameters_HasAgentsArray()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
tool.Parameters.Properties.Should().ContainKey("agents");
|
|
tool.Parameters.Required.Should().Contain("agents");
|
|
}
|
|
|
|
// ═══════════════════════════════════════════
|
|
// 입력 검증
|
|
// ═══════════════════════════════════════════
|
|
|
|
[Fact]
|
|
public async Task ExecuteAsync_MissingAgents_ReturnsFail()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
var json = JsonDocument.Parse("{}").RootElement;
|
|
|
|
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
|
result.Success.Should().BeFalse();
|
|
result.Output.Should().Contain("agents");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteAsync_EmptyAgentsArray_ReturnsFail()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
var json = JsonDocument.Parse("""{"agents": []}""").RootElement;
|
|
|
|
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
|
result.Success.Should().BeFalse();
|
|
result.Output.Should().Contain("empty");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteAsync_MissingId_ReturnsFail()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
var json = JsonDocument.Parse("""{"agents": [{"task": "do something"}]}""").RootElement;
|
|
|
|
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
|
result.Success.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteAsync_MissingTask_ReturnsFail()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
var json = JsonDocument.Parse("""{"agents": [{"id": "a1"}]}""").RootElement;
|
|
|
|
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
|
result.Success.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteAsync_DuplicateIds_ReturnsFail()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
var json = JsonDocument.Parse("""
|
|
{
|
|
"agents": [
|
|
{"id": "a1", "task": "task1"},
|
|
{"id": "a1", "task": "task2"}
|
|
]
|
|
}
|
|
""").RootElement;
|
|
|
|
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
|
result.Success.Should().BeFalse();
|
|
result.Output.Should().Contain("Duplicate");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteAsync_AgentsNotArray_ReturnsFail()
|
|
{
|
|
var tool = new SpawnAgentsTool();
|
|
var json = JsonDocument.Parse("""{"agents": "not an array"}""").RootElement;
|
|
|
|
var result = await tool.ExecuteAsync(json, CreateMinimalContext());
|
|
result.Success.Should().BeFalse();
|
|
}
|
|
|
|
// ═══════════════════════════════════════════
|
|
// 서브에이전트 재귀 차단 검증
|
|
// ═══════════════════════════════════════════
|
|
|
|
[Fact]
|
|
public void AllSubAgentProfiles_DisableSpawnAgents()
|
|
{
|
|
// spawn_agents는 모든 서브에이전트 프로파일에서 비활성화되어야 함 (재귀 방지)
|
|
foreach (var name in SubAgentProfileCatalog.AllProfileNames)
|
|
{
|
|
var profile = SubAgentProfileCatalog.Get(name);
|
|
profile.DisabledToolNames.Should().Contain("spawn_agents",
|
|
$"profile '{name}' should disable spawn_agents to prevent recursion");
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════
|
|
// 유틸
|
|
// ═══════════════════════════════════════════
|
|
|
|
private static AgentContext CreateMinimalContext()
|
|
{
|
|
return new AgentContext
|
|
{
|
|
WorkFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
|
};
|
|
}
|
|
}
|