feat: enable 'always approve for session' for runtime tools

Runtime tool permission requests (read, write, shell, store_memory) now
resolve stable approval names via fallback in the sidecar approval
coordinator, enabling the 'Always approve' button and session-level
auto-approval for built-in tools.

Backend:
- Add fallback tool names for PermissionRequestRead (read),
  PermissionRequestWrite (write), PermissionRequestShell (shell),
  and PermissionRequestMemory (store_memory)
- Add autoApprovedToolName parameter to RequiresToolCallApproval
  for permission-kind-based session approval matching
- Track tool.execution_start events in ToolNamesByCallId for
  supplementary exact-name resolution

Frontend:
- Use approval.toolName ?? approval.permissionKind as fallback key
  in resolveSessionApproval and ApprovalBanner
- Add shell, read, write permission-kind entries to builtin approval
  tool definitions so pruning preserves session overrides

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-27 21:20:14 +01:00
co-authored by Copilot
parent 154787c336
commit 3ec69d990b
9 changed files with 199 additions and 15 deletions
@@ -58,6 +58,31 @@ public sealed class CopilotTurnExecutionStateTests
Assert.Equal("agent-1", observedAgent.AgentId);
}
[Fact]
public void ObserveSessionEvent_ToolExecutionStart_TracksToolNameByCallId()
{
RunTurnCommandDto command = CreateCommand();
CopilotTurnExecutionState state = new(command);
state.ObserveSessionEvent(
command.Pattern.Agents[0],
SessionEvent.FromJson(
"""
{
"type": "tool.execution_start",
"data": {
"toolCallId": "tool-call-1",
"toolName": "view"
},
"id": "33333333-3333-3333-3333-333333333333",
"timestamp": "2026-03-27T00:00:00Z"
}
"""));
Assert.True(state.ToolNamesByCallId.TryGetValue("tool-call-1", out string? toolName));
Assert.Equal("view", toolName);
}
[Fact]
public async Task EmitThinkingIfNeeded_DoesNotDuplicateQueuedThinkingActivity()
{
@@ -766,7 +766,29 @@ public sealed class CopilotWorkflowRunnerTests
}
[Fact]
public void TryGetApprovalToolName_ReadsMcpCustomAndHookRequests()
public void RequiresToolCallApproval_HonorsRuntimeApprovalAliases()
{
ApprovalPolicyDto policy = new()
{
Rules =
[
new ApprovalCheckpointRuleDto
{
Kind = "tool-call",
AgentIds = ["agent-1"],
},
],
AutoApprovedToolNames = ["read", "store_memory"],
};
Assert.False(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", "view", "read"));
Assert.False(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", "remember_fact", "store_memory"));
Assert.True(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", "write_file", "write"));
Assert.True(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", "git.status"));
}
[Fact]
public void TryGetApprovalToolName_ResolvesDirectNamesAndRuntimeFallbacks()
{
Assert.True(
CopilotApprovalCoordinator.TryGetApprovalToolName(
@@ -804,7 +826,7 @@ public sealed class CopilotWorkflowRunnerTests
out string? hookToolName));
Assert.Equal("web_fetch", hookToolName);
Assert.False(
Assert.True(
CopilotApprovalCoordinator.TryGetApprovalToolName(
new PermissionRequestShell
{
@@ -818,7 +840,49 @@ public sealed class CopilotWorkflowRunnerTests
CanOfferSessionApproval = false,
},
out string? shellToolName));
Assert.Null(shellToolName);
Assert.Equal("shell", shellToolName);
}
[Fact]
public void TryGetApprovalToolName_FallsBackToRuntimeApprovalAliasesWhenLookupMissing()
{
Assert.True(
CopilotApprovalCoordinator.TryGetApprovalToolName(
new PermissionRequestRead
{
Kind = "read",
ToolCallId = "tool-call-read",
Intention = "Inspect a file",
Path = "README.md",
},
out string? readToolName));
Assert.Equal("read", readToolName);
Assert.True(
CopilotApprovalCoordinator.TryGetApprovalToolName(
new PermissionRequestWrite
{
Kind = "write",
ToolCallId = "tool-call-write",
Intention = "Update a file",
FileName = "README.md",
Diff = "@@ -1 +1 @@",
},
out string? writeToolName));
Assert.Equal("write", writeToolName);
Assert.True(
CopilotApprovalCoordinator.TryGetApprovalToolName(
new PermissionRequestMemory
{
Kind = "memory",
ToolCallId = "tool-call-memory",
Subject = "repo conventions",
Fact = "Use Bun for script execution.",
Citations = "package.json",
},
out string? memoryToolName));
Assert.Equal("store_memory", memoryToolName);
}
[Fact]