From bf2a454ef2cd1346d707d5e7e9e62dcd45214851 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Mon, 30 Mar 2026 00:36:52 +0200 Subject: [PATCH] fix: auto-allow internal orchestration tools Allow SDK-managed orchestration tools to bypass pre-tool approval prompts so Aryx matches Copilot CLI behavior for non-side-effectful meta tools. Keep store_memory under the existing memory approval category. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Services/CopilotSessionHooks.cs | 16 ++++++++++++ .../CopilotSessionHooksTests.cs | 26 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/sidecar/src/Aryx.AgentHost/Services/CopilotSessionHooks.cs b/sidecar/src/Aryx.AgentHost/Services/CopilotSessionHooks.cs index 2983fb6..4657724 100644 --- a/sidecar/src/Aryx.AgentHost/Services/CopilotSessionHooks.cs +++ b/sidecar/src/Aryx.AgentHost/Services/CopilotSessionHooks.cs @@ -11,14 +11,30 @@ internal static class CopilotSessionHooks private const string AllowDecision = "allow"; private const string AskDecision = "ask"; private const string DenyDecision = "deny"; + private const string ExitPlanModeToolName = "exit_plan_mode"; + private const string FetchCopilotCliDocumentationToolName = "fetch_copilot_cli_documentation"; private const string HandoffToolPrefix = "handoff_to_"; + private const string ListAgentsToolName = "list_agents"; + private const string ReadAgentToolName = "read_agent"; private const string ReportIntentToolName = "report_intent"; + private const string SkillToolName = "skill"; + private const string SqlToolName = "sql"; + private const string TaskToolName = "task"; private const string TaskCompleteToolName = "task_complete"; + private const string UpdateTodoToolName = "update_todo"; private static readonly HashSet AlwaysAllowedToolNames = new(StringComparer.OrdinalIgnoreCase) { AskUserToolName, + ExitPlanModeToolName, + FetchCopilotCliDocumentationToolName, + ListAgentsToolName, + ReadAgentToolName, ReportIntentToolName, + SkillToolName, + SqlToolName, + TaskToolName, TaskCompleteToolName, + UpdateTodoToolName, }; private static readonly JsonSerializerOptions HookJsonOptions = CreateHookJsonOptions(); diff --git a/sidecar/tests/Aryx.AgentHost.Tests/CopilotSessionHooksTests.cs b/sidecar/tests/Aryx.AgentHost.Tests/CopilotSessionHooksTests.cs index 2202396..842cee1 100644 --- a/sidecar/tests/Aryx.AgentHost.Tests/CopilotSessionHooksTests.cs +++ b/sidecar/tests/Aryx.AgentHost.Tests/CopilotSessionHooksTests.cs @@ -108,11 +108,19 @@ public sealed class CopilotSessionHooksTests [Theory] [InlineData("ask_user")] + [InlineData("exit_plan_mode")] + [InlineData("fetch_copilot_cli_documentation")] + [InlineData("list_agents")] + [InlineData("read_agent")] [InlineData("report_intent")] + [InlineData("skill")] + [InlineData("sql")] + [InlineData("task")] [InlineData("task_complete")] + [InlineData("update_todo")] [InlineData("handoff_to_2")] [InlineData("handoff_to_specialist")] - public async Task Create_PreToolUseAutoAllowsInfrastructureTools(string toolName) + public async Task Create_PreToolUseAutoAllowsInternalOrchestrationTools(string toolName) { RunTurnCommandDto command = CreateCommandWithToolApproval(); SessionHooks hooks = CopilotSessionHooks.Create(command, command.Pattern.Agents[0], ResolvedHookSet.Empty, new RecordingHookCommandRunner()); @@ -127,6 +135,22 @@ public sealed class CopilotSessionHooksTests Assert.Equal("allow", decision?.PermissionDecision); } + [Fact] + public async Task Create_PreToolUseKeepsStoreMemoryUnderApprovalPolicy() + { + RunTurnCommandDto command = CreateCommandWithToolApproval(); + SessionHooks hooks = CopilotSessionHooks.Create(command, command.Pattern.Agents[0], ResolvedHookSet.Empty, new RecordingHookCommandRunner()); + + PreToolUseHookOutput? decision = await hooks.OnPreToolUse!( + new PreToolUseHookInput + { + ToolName = "store_memory", + }, + null!); + + Assert.Equal("ask", decision?.PermissionDecision); + } + [Fact] public async Task Create_RunsConfiguredNonPreToolHooks() {