코드 탭 워크스페이스 권한 정합성 수정 및 회귀 테스트 추가
- AgentLoopService에 RuntimeWorkFolderOverride를 추가해 Code/Cowork 실행이 settings 기본 경로보다 현재 대화 WorkFolder를 우선 사용하도록 정리 - ChatWindow에서 RunAgentLoopAsync 실행 시 conversation.WorkFolder를 루프에 직접 주입하고 사내 모드 권한 안내도 같은 런타임 워크스페이스 기준으로 맞춤 - AgentLoopE2ETests에 워크스페이스 override 우선 적용, 사내 모드 내부 경로 무승인, 외부 경로 승인 강제 회귀를 추가 - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_workspace_permission_fix\\ -p:IntermediateOutputPath=obj\\verify_workspace_permission_fix\\ / 경고 0 오류 0 - 검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter FullyQualifiedName~RunAsync_CodeRuntimeWorkspaceOverride_PrefersConversationWorkspaceOverSettingsFolder|FullyQualifiedName~RunAsync_InternalMode_BypassPermissions_AllowsWorkspaceWriteWithoutPrompt|FullyQualifiedName~RunAsync_InternalMode_BypassPermissions_RequestsApprovalForPathOutsideWorkspace|FullyQualifiedName~RunAsync_EmptyWorkspace_BlocksExternalFallbackAndRecoversToFileWrite|FullyQualifiedName~RunAsync_EmptyWorkspace_DisallowsSkillManagerAndRecoversToFileWrite|FullyQualifiedName~RunAsync_TextEmbeddedToolCall_RecoversAndExecutesFileWrite / 통과 6
This commit is contained in:
@@ -637,6 +637,151 @@ public class AgentLoopE2ETests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_CodeRuntimeWorkspaceOverride_PrefersConversationWorkspaceOverSettingsFolder()
|
||||
{
|
||||
var settingsDir = Path.Combine(Path.GetTempPath(), "axcopilot-settings-workspace-" + Guid.NewGuid().ToString("N"));
|
||||
var conversationDir = Path.Combine(Path.GetTempPath(), "axcopilot-conversation-workspace-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(settingsDir);
|
||||
Directory.CreateDirectory(conversationDir);
|
||||
try
|
||||
{
|
||||
using var server = new FakeOllamaServer(
|
||||
[
|
||||
BuildToolCallResponse("file_write", new { path = "index.html", content = "<html><body>override</body></html>" }, "write to conversation workspace"),
|
||||
BuildTextResponse("?꾨즺"),
|
||||
]);
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
settings.Settings.Llm.WorkFolder = settingsDir;
|
||||
settings.Settings.Llm.CodeWorkFolder = settingsDir;
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = ToolRegistry.CreateDefault();
|
||||
var loop = new AgentLoopService(llm, tools, settings)
|
||||
{
|
||||
ActiveTab = "Code",
|
||||
RuntimeWorkFolderOverride = conversationDir,
|
||||
};
|
||||
|
||||
var result = await loop.RunAsync(
|
||||
[
|
||||
new ChatMessage { Role = "user", Content = "index.html ?뚯씪 留뚮뱾?댁쨾" }
|
||||
]);
|
||||
|
||||
result.Should().Contain("?꾨즺");
|
||||
File.Exists(Path.Combine(conversationDir, "index.html")).Should().BeTrue();
|
||||
File.Exists(Path.Combine(settingsDir, "index.html")).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (Directory.Exists(settingsDir)) Directory.Delete(settingsDir, true); } catch { }
|
||||
try { if (Directory.Exists(conversationDir)) Directory.Delete(conversationDir, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_InternalMode_BypassPermissions_AllowsWorkspaceWriteWithoutPrompt()
|
||||
{
|
||||
var workspaceDir = Path.Combine(Path.GetTempPath(), "axcopilot-internal-workspace-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workspaceDir);
|
||||
try
|
||||
{
|
||||
using var server = new FakeOllamaServer(
|
||||
[
|
||||
BuildToolCallResponse("file_write", new { path = "inside.txt", content = "ok" }, "write inside workspace"),
|
||||
BuildTextResponse("?꾨즺"),
|
||||
]);
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
settings.Settings.OperationMode = OperationModePolicy.InternalMode;
|
||||
settings.Settings.Llm.WorkFolder = workspaceDir;
|
||||
settings.Settings.Llm.CodeWorkFolder = workspaceDir;
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = ToolRegistry.CreateDefault();
|
||||
var loop = new AgentLoopService(llm, tools, settings)
|
||||
{
|
||||
ActiveTab = "Code",
|
||||
RuntimeWorkFolderOverride = workspaceDir,
|
||||
};
|
||||
|
||||
var promptCount = 0;
|
||||
loop.AskPermissionCallback = (_, _) =>
|
||||
{
|
||||
promptCount++;
|
||||
return Task.FromResult(false);
|
||||
};
|
||||
|
||||
var result = await loop.RunAsync(
|
||||
[
|
||||
new ChatMessage { Role = "user", Content = "inside.txt ?뚯씪 留뚮뱾?댁쨾" }
|
||||
]);
|
||||
|
||||
result.Should().Contain("?꾨즺");
|
||||
File.Exists(Path.Combine(workspaceDir, "inside.txt")).Should().BeTrue();
|
||||
promptCount.Should().Be(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (Directory.Exists(workspaceDir)) Directory.Delete(workspaceDir, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_InternalMode_BypassPermissions_RequestsApprovalForPathOutsideWorkspace()
|
||||
{
|
||||
var workspaceDir = Path.Combine(Path.GetTempPath(), "axcopilot-internal-base-" + Guid.NewGuid().ToString("N"));
|
||||
var externalDir = Path.Combine(Path.GetTempPath(), "axcopilot-internal-external-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workspaceDir);
|
||||
Directory.CreateDirectory(externalDir);
|
||||
try
|
||||
{
|
||||
var externalTarget = Path.Combine(externalDir, "outside.txt");
|
||||
using var server = new FakeOllamaServer(
|
||||
[
|
||||
BuildToolCallResponse("file_write", new { path = externalTarget, content = "blocked" }, "write outside workspace"),
|
||||
BuildTextResponse("?꾨즺"),
|
||||
]);
|
||||
|
||||
var settings = BuildLoopSettings(server.Endpoint);
|
||||
settings.Settings.OperationMode = OperationModePolicy.InternalMode;
|
||||
settings.Settings.Llm.WorkFolder = workspaceDir;
|
||||
settings.Settings.Llm.CodeWorkFolder = workspaceDir;
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = ToolRegistry.CreateDefault();
|
||||
var loop = new AgentLoopService(llm, tools, settings)
|
||||
{
|
||||
ActiveTab = "Code",
|
||||
RuntimeWorkFolderOverride = workspaceDir,
|
||||
};
|
||||
|
||||
var promptCount = 0;
|
||||
var events = new List<AgentEvent>();
|
||||
loop.EventOccurred += evt => events.Add(evt);
|
||||
loop.AskPermissionCallback = (_, _) =>
|
||||
{
|
||||
promptCount++;
|
||||
return Task.FromResult(false);
|
||||
};
|
||||
|
||||
var result = await loop.RunAsync(
|
||||
[
|
||||
new ChatMessage { Role = "user", Content = "외부 경로에 파일을 써줘" }
|
||||
]);
|
||||
|
||||
result.Should().Contain("?꾨즺");
|
||||
File.Exists(externalTarget).Should().BeFalse();
|
||||
promptCount.Should().Be(1);
|
||||
events.Should().Contain(e =>
|
||||
(e.Type == AgentEventType.PermissionDenied || e.Type == AgentEventType.Error) &&
|
||||
e.ToolName == "file_write");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (Directory.Exists(workspaceDir)) Directory.Delete(workspaceDir, true); } catch { }
|
||||
try { if (Directory.Exists(externalDir)) Directory.Delete(externalDir, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private static SettingsService BuildLoopSettings(string endpoint)
|
||||
{
|
||||
var settings = new SettingsService();
|
||||
|
||||
Reference in New Issue
Block a user