diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index f58d174..05b1201 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -246,8 +246,9 @@ This is a good example of a cross-cutting concern that spans multiple layers wit
Tooling is deliberately split into two levels:
+- **dynamic runtime tools** reported by the Copilot CLI, with a fallback catalog for startup/offline cases
- **global definitions** for MCP servers and LSP profiles
-- **pattern defaults** for which known tools can bypass manual approval
+- **pattern defaults** for which known runtime tools can bypass manual approval
- **per-session overrides** for both tool enablement and tool auto-approval
This lets the application treat tooling as reusable workspace capability while still preserving session-level control and safety.
diff --git a/HANDOVER.md b/HANDOVER.md
index 94cf9ae..077e70a 100644
--- a/HANDOVER.md
+++ b/HANDOVER.md
@@ -8,7 +8,7 @@ This builds on the existing approval-checkpoint system:
- `final-response` approvals still work the same way
- `tool-call` checkpoints still decide **whether a tool call needs approval at all**
-- the new feature decides **which known tools can be auto-approved instead of surfacing a manual approval**
+- the new feature decides **which known runtime tools can be auto-approved instead of surfacing a manual approval**
The approval queue work from earlier is unchanged and still applies.
@@ -80,21 +80,39 @@ Unknown or non-tool-specific permission requests still require manual approval w
Use the shared helper in `src/shared/domain/tooling.ts`:
```ts
-listApprovalToolDefinitions(workspace.settings.tooling)
+listApprovalToolDefinitions(workspace.settings.tooling, sidecarCapabilities?.runtimeTools)
```
This is the canonical source for UI rendering and validation.
+Important:
+
+- runtime tools should come from `describeSidecarCapabilities().runtimeTools` when available
+- the shared helper falls back to a conservative built-in catalog until capabilities load or when the CLI cannot report tools
+- the helper also merges configured MCP server tools and derived LSP tools
+
Each returned item includes:
- `id` → the runtime tool identifier used by approvals
- `label` → human-readable label for the UI
-- `kind` → `mcp`, `lsp`, or `mixed`
+- `kind` → `builtin`, `mcp`, `lsp`, or `mixed`
- `providerIds`
- `providerNames`
Do **not** re-derive tool IDs in the renderer.
+### Runtime tool IDs
+
+For Copilot CLI runtime tools, the ID is the tool name returned by the sidecar capability payload.
+
+Examples:
+
+- `web_fetch`
+- `view`
+- `glob`
+
+Descriptions come from the sidecar when available and can be surfaced in the UI.
+
### MCP tool IDs
For MCP tools, the runtime ID is the raw tool name from `server.tools`.
@@ -157,7 +175,7 @@ The backend now enforces and maintains tool references consistently:
- saving a pattern rejects unknown `autoApprovedToolNames`
- updating session approval settings rejects unknown tool IDs
- scratchpad sessions reject non-empty session tool auto-approval overrides
-- workspace load prunes stale tool IDs from patterns and sessions
+- workspace load normalizes approval state and the app service prunes stale tool IDs after runtime tools/capabilities are available
- saving or deleting MCP/LSP definitions also prunes stale tool IDs from patterns and sessions
This is intentional: stored approval defaults should only reference tools that still exist.
@@ -180,13 +198,17 @@ Existing UI can keep working without changes, but the UX agent can now surface b
- `src/shared/domain/approval.ts`
- pattern defaults, session override model, effective-policy helpers, pruning/validation helpers
- `src/shared/domain/tooling.ts`
- - canonical `listApprovalToolDefinitions(...)` helper
+ - canonical merged runtime/MCP/LSP `listApprovalToolDefinitions(...)` helper
+- `src/shared/contracts/sidecar.ts`
+ - runtime tool capability payload
- `src/shared/domain/session.ts`
- session approval settings + effective pattern merge helper
- `src/main/EryxAppService.ts`
- pattern validation, session override IPC handler, tooling cleanup, effective-pattern merge
- `src/main/persistence/workspaceRepository.ts`
- - load-time normalization + pruning for stale tool IDs
+ - load-time normalization without prematurely pruning dynamic runtime tool IDs
+- `sidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cs`
+ - dynamic Copilot CLI runtime tool discovery via `tools.list`
- `src/shared/contracts/ipc.ts`
- `src/shared/contracts/channels.ts`
- `src\preload\index.ts`
@@ -198,55 +220,27 @@ Existing UI can keep working without changes, but the UX agent can now surface b
## UX work for the frontend agent
-### 1. Pattern editor: default tool auto-approval
+All UX tasks from this section have been implemented in commit `4ff5cbb`.
-In `PatternEditor`, add a section that renders `listApprovalToolDefinitions(workspace.settings.tooling)`.
+### 1. Pattern editor: default tool auto-approval — ✅ Done
-Recommended behavior:
+`PatternEditor` renders `listApprovalToolDefinitions(workspace.settings.tooling, sidecarCapabilities?.runtimeTools)` with toggles writing to `pattern.approvalPolicy.autoApprovedToolNames`. Runtime tools come from the sidecar when available and fall back to the shared built-in catalog until capabilities load.
-- show every available approval tool with a toggle
-- write the selected tool IDs to `pattern.approvalPolicy.autoApprovedToolNames`
-- keep this separate from the existing `tool-call` / `final-response` checkpoint toggles
-- if there are no tools, show an empty state instead of an interactive list
+### 2. Activity panel: per-session override — ✅ Done
-### 2. Activity panel: per-session override
+Right-side Activity panel shows "Inheriting pattern defaults" / "Custom for this session" badge with "Reset to pattern" action. Toggle rows for each tool call `updateSessionApprovalSettings(...)`.
-In the right-side Activity panel, add a per-session auto-approval section for tools.
+### 3. Disable while running — ✅ Done
-Recommended behavior:
+Per-session override controls disabled when `session.status === 'running'`. Scratchpad shows non-interactive explanation.
-- base the list on `listApprovalToolDefinitions(workspace.settings.tooling)`
-- show the current **effective** state
-- distinguish inherited vs overridden state in the UI
-- allow resetting the session to inherit the pattern defaults
-- call `updateSessionApprovalSettings(...)` when the user changes the override
+### 4. Surface tool context in approval UI — ✅ Done
-Suggested UX:
+`approval.toolName` displayed in both the active approval banner and queued approval list when present.
-- `Inheriting pattern defaults` badge/state when `session.approvalSettings` is `undefined`
-- `Custom for this session` badge/state when the override object exists
-- `Reset to pattern` action that calls `updateSessionApprovalSettings({ sessionId })`
+### 5. Avoid ID duplication logic in the renderer — ✅ Done
-### 3. Disable while running
-
-Match the current tools behavior:
-
-- disable the per-session override controls while `session.status === 'running'`
-- scratchpad sessions should show a non-interactive explanation because tool auto-approval does not apply there
-
-### 4. Surface tool context in approval UI
-
-Optional but recommended:
-
-- show `approval.toolName` in the active approval banner / queued approval list when present
-- this is now available for tool-specific approval requests
-
-### 5. Avoid ID duplication logic in the renderer
-
-Important:
-
-- do not manually rebuild LSP tool IDs in UI code
-- use the shared helper output and persist the `id` values from it
+All tool ID resolution uses `listApprovalToolDefinitions()` output exclusively.
## Validation commands
diff --git a/README.md b/README.md
index 6b7cd4c..1936807 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,7 @@ Eryx includes connection status in the app so you can quickly tell whether Copil
Use a simple single-agent setup to begin, or choose a saved multi-agent pattern when you want a more structured workflow.
5. **Configure optional tooling**
- If you want MCP or LSP support, add the global definitions in settings and then enable the ones you want for the current session from the Activity panel. You can also set pattern-level tool auto-approval defaults and override them per session.
+ If you want MCP or LSP support, add the global definitions in settings and then enable the ones you want for the current session from the Activity panel. Eryx also surfaces Copilot CLI runtime tools for tool auto-approval, and you can set pattern-level defaults and override them per session.
6. **Start working**
Ask a question, describe a task, or explore a project. As the run progresses, you can watch the participating agents and keep the session for later.
diff --git a/sidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cs b/sidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cs
index e76ab00..40f33fe 100644
--- a/sidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cs
+++ b/sidecar/src/Eryx.AgentHost/Contracts/ProtocolModels.cs
@@ -69,6 +69,13 @@ public sealed class SidecarModelCapabilityDto
public string? DefaultReasoningEffort { get; init; }
}
+public sealed class SidecarRuntimeToolDto
+{
+ public string Id { get; init; } = string.Empty;
+ public string Label { get; init; } = string.Empty;
+ public string? Description { get; init; }
+}
+
public sealed class SidecarConnectionDiagnosticsDto
{
public string Status { get; init; } = "copilot-error";
@@ -103,6 +110,7 @@ public sealed class SidecarCapabilitiesDto
public string Runtime { get; init; } = "dotnet-maf";
public Dictionary Modes { get; init; } = new(StringComparer.OrdinalIgnoreCase);
public IReadOnlyList Models { get; init; } = [];
+ public IReadOnlyList RuntimeTools { get; init; } = [];
public SidecarConnectionDiagnosticsDto Connection { get; init; } = new();
}
diff --git a/sidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cs b/sidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cs
index 985eb83..991e06e 100644
--- a/sidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cs
+++ b/sidecar/src/Eryx.AgentHost/Services/SidecarProtocolHost.cs
@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
using GitHub.Copilot.SDK;
+using GitHub.Copilot.SDK.Rpc;
using Eryx.AgentHost.Contracts;
namespace Eryx.AgentHost.Services;
@@ -187,6 +188,7 @@ public sealed class SidecarProtocolHost
private static async Task BuildCapabilitiesAsync(CancellationToken cancellationToken)
{
IReadOnlyList models = [];
+ IReadOnlyList runtimeTools = [];
CopilotCliContext cliContext;
SidecarConnectionDiagnosticsDto connection;
SidecarCopilotCliVersionDiagnosticsDto? cliVersion = null;
@@ -227,6 +229,14 @@ public sealed class SidecarProtocolHost
cancellationToken).ConfigureAwait(false);
models = await ListAvailableModelsAsync(client, cancellationToken).ConfigureAwait(false);
+ try
+ {
+ runtimeTools = await ListAvailableRuntimeToolsAsync(client, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception exception)
+ {
+ Console.Error.WriteLine($"[eryx sidecar] Failed to list available Copilot runtime tools: {exception.Message}");
+ }
cliVersion = await cliVersionTask.ConfigureAwait(false);
connection = CreateReadyConnectionDiagnostics(cliContext.CliPath, models.Count, cliVersion, account);
}
@@ -241,6 +251,7 @@ public sealed class SidecarProtocolHost
{
Modes = BuildModeCapabilities(),
Models = models,
+ RuntimeTools = runtimeTools,
Connection = connection,
};
}
@@ -284,6 +295,24 @@ public sealed class SidecarProtocolHost
.ToList();
}
+ private static async Task> ListAvailableRuntimeToolsAsync(
+ CopilotClient client,
+ CancellationToken cancellationToken)
+ {
+ ToolsListResult result = await client.Rpc.Tools.ListAsync(null!, cancellationToken).ConfigureAwait(false);
+ return result.Tools
+ .Where(tool => !string.IsNullOrWhiteSpace(tool.Name))
+ .Select(tool => new SidecarRuntimeToolDto
+ {
+ Id = tool.Name.Trim(),
+ Label = tool.Name.Trim(),
+ Description = string.IsNullOrWhiteSpace(tool.Description) ? null : tool.Description.Trim(),
+ })
+ .DistinctBy(tool => tool.Id, StringComparer.OrdinalIgnoreCase)
+ .OrderBy(tool => tool.Label, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+ }
+
private static bool IsReasoningEffort(string? value)
{
return value is "low" or "medium" or "high" or "xhigh";
diff --git a/sidecar/tests/Eryx.AgentHost.Tests/SidecarProtocolHostTests.cs b/sidecar/tests/Eryx.AgentHost.Tests/SidecarProtocolHostTests.cs
index eae745d..c3adffa 100644
--- a/sidecar/tests/Eryx.AgentHost.Tests/SidecarProtocolHostTests.cs
+++ b/sidecar/tests/Eryx.AgentHost.Tests/SidecarProtocolHostTests.cs
@@ -37,6 +37,10 @@ public sealed class SidecarProtocolHostTests
JsonElement model = Assert.Single(models);
Assert.Equal("gpt-5.4", model.GetProperty("id").GetString());
Assert.Equal("medium", model.GetProperty("defaultReasoningEffort").GetString());
+ JsonElement[] runtimeTools = capabilities.GetProperty("runtimeTools").EnumerateArray().ToArray();
+ JsonElement runtimeTool = Assert.Single(runtimeTools);
+ Assert.Equal("web_fetch", runtimeTool.GetProperty("id").GetString());
+ Assert.Equal("web_fetch", runtimeTool.GetProperty("label").GetString());
JsonElement connection = capabilities.GetProperty("connection");
Assert.Equal("ready", connection.GetProperty("status").GetString());
Assert.Equal(@"C:\tools\copilot\copilot.exe", connection.GetProperty("copilotCliPath").GetString());
@@ -403,6 +407,15 @@ public sealed class SidecarProtocolHostTests
DefaultReasoningEffort = "medium",
},
],
+ RuntimeTools =
+ [
+ new SidecarRuntimeToolDto
+ {
+ Id = "web_fetch",
+ Label = "web_fetch",
+ Description = "Fetch content from the web.",
+ },
+ ],
Connection = new SidecarConnectionDiagnosticsDto
{
Status = "ready",
diff --git a/src/main/EryxAppService.ts b/src/main/EryxAppService.ts
index 174a478..8bf3ef1 100644
--- a/src/main/EryxAppService.ts
+++ b/src/main/EryxAppService.ts
@@ -103,6 +103,16 @@ function isBuiltinPattern(patternId: string): boolean {
return patternId.startsWith('pattern-');
}
+function equalStringArrays(left?: readonly string[], right?: readonly string[]): boolean {
+ const normalizedLeft = left ?? [];
+ const normalizedRight = right ?? [];
+ if (normalizedLeft.length !== normalizedRight.length) {
+ return false;
+ }
+
+ return normalizedLeft.every((value, index) => value === normalizedRight[index]);
+}
+
export class EryxAppService extends EventEmitter {
private readonly workspaceRepository = new WorkspaceRepository();
private readonly sidecar = new SidecarClient();
@@ -124,7 +134,8 @@ export class EryxAppService extends EventEmitter {
async loadWorkspace(): Promise {
if (!this.workspace) {
this.workspace = await this.workspaceRepository.load();
- if (this.failInterruptedPendingApprovals(this.workspace)) {
+ const didPruneApprovalTools = await this.pruneUnavailableApprovalTools(this.workspace);
+ if (didPruneApprovalTools || this.failInterruptedPendingApprovals(this.workspace)) {
await this.workspaceRepository.save(this.workspace);
}
}
@@ -200,9 +211,10 @@ export class EryxAppService extends EventEmitter {
async savePattern(pattern: PatternDefinition): Promise {
const workspace = await this.loadWorkspace();
+ const knownApprovalToolNames = await this.listKnownApprovalToolNames(workspace);
const issues = validatePatternDefinition(
pattern,
- this.listKnownApprovalToolNames(workspace),
+ knownApprovalToolNames,
).filter((issue) => issue.level === 'error');
if (issues.length > 0) {
throw new Error(issues[0].message);
@@ -281,7 +293,7 @@ export class EryxAppService extends EventEmitter {
workspace.settings.tooling.mcpServers.push(candidate);
}
- this.pruneUnavailableApprovalTools(workspace);
+ await this.pruneUnavailableApprovalTools(workspace);
return this.persistAndBroadcast(workspace);
}
@@ -299,7 +311,7 @@ export class EryxAppService extends EventEmitter {
};
}
- this.pruneUnavailableApprovalTools(workspace);
+ await this.pruneUnavailableApprovalTools(workspace);
return this.persistAndBroadcast(workspace);
}
@@ -328,7 +340,7 @@ export class EryxAppService extends EventEmitter {
workspace.settings.tooling.lspProfiles.push(candidate);
}
- this.pruneUnavailableApprovalTools(workspace);
+ await this.pruneUnavailableApprovalTools(workspace);
return this.persistAndBroadcast(workspace);
}
@@ -346,7 +358,7 @@ export class EryxAppService extends EventEmitter {
};
}
- this.pruneUnavailableApprovalTools(workspace);
+ await this.pruneUnavailableApprovalTools(workspace);
return this.persistAndBroadcast(workspace);
}
@@ -703,7 +715,7 @@ export class EryxAppService extends EventEmitter {
throw new Error('Scratchpad sessions do not support tool auto-approval settings.');
}
- const knownToolNames = new Set(this.listKnownApprovalToolNames(workspace));
+ const knownToolNames = new Set(await this.listKnownApprovalToolNames(workspace));
const unknownToolName = settings?.autoApprovedToolNames.find((toolName) => !knownToolNames.has(toolName));
if (unknownToolName) {
throw new Error(`Unknown approval tool "${unknownToolName}".`);
@@ -1172,23 +1184,42 @@ export class EryxAppService extends EventEmitter {
return normalizePatternModels(patternWithApprovalSettings, modelCatalog);
}
- private listKnownApprovalToolNames(workspace: WorkspaceState): string[] {
- return listApprovalToolNames(workspace.settings.tooling);
+ private async listKnownApprovalToolNames(workspace: WorkspaceState): Promise {
+ const capabilities = await this.loadSidecarCapabilities();
+ const runtimeTools = capabilities.runtimeTools.length > 0 ? capabilities.runtimeTools : undefined;
+ return listApprovalToolNames(workspace.settings.tooling, runtimeTools);
}
- private pruneUnavailableApprovalTools(workspace: WorkspaceState): void {
- const knownToolNames = this.listKnownApprovalToolNames(workspace);
+ private async pruneUnavailableApprovalTools(workspace: WorkspaceState): Promise {
+ const knownToolNames = await this.listKnownApprovalToolNames(workspace);
+ let changed = false;
for (const pattern of workspace.patterns) {
- pattern.approvalPolicy = pruneApprovalPolicyTools(pattern.approvalPolicy, knownToolNames);
+ const nextPolicy = pruneApprovalPolicyTools(pattern.approvalPolicy, knownToolNames);
+ if (!equalStringArrays(
+ pattern.approvalPolicy?.autoApprovedToolNames,
+ nextPolicy?.autoApprovedToolNames,
+ )) {
+ pattern.approvalPolicy = nextPolicy;
+ changed = true;
+ }
}
for (const session of workspace.sessions) {
- session.approvalSettings = pruneSessionApprovalSettings(
+ const nextSettings = pruneSessionApprovalSettings(
session.approvalSettings,
knownToolNames,
);
+ if (!equalStringArrays(
+ session.approvalSettings?.autoApprovedToolNames,
+ nextSettings?.autoApprovedToolNames,
+ )) {
+ session.approvalSettings = nextSettings;
+ changed = true;
+ }
}
+
+ return changed;
}
private buildRunTurnToolingConfig(
diff --git a/src/main/persistence/workspaceRepository.ts b/src/main/persistence/workspaceRepository.ts
index 63834a4..1034a5d 100644
--- a/src/main/persistence/workspaceRepository.ts
+++ b/src/main/persistence/workspaceRepository.ts
@@ -5,7 +5,6 @@ import type { PatternDefinition } from '@shared/domain/pattern';
import { mergeScratchpadProject } from '@shared/domain/project';
import { normalizeSessionRunRecords } from '@shared/domain/runTimeline';
import {
- listApprovalToolNames,
normalizeSessionToolingSelection,
normalizeWorkspaceSettings,
} from '@shared/domain/tooling';
@@ -13,8 +12,6 @@ import {
normalizeApprovalPolicy,
normalizePendingApprovalState,
normalizeSessionApprovalSettings,
- pruneApprovalPolicyTools,
- pruneSessionApprovalSettings,
} from '@shared/domain/approval';
import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspace';
import { nowIso } from '@shared/utils/ids';
@@ -68,26 +65,19 @@ export class WorkspaceRepository {
const projects = mergeScratchpadProject(stored.projects ?? [], this.scratchpadPath);
const settings = normalizeWorkspaceSettings(stored.settings);
- const knownToolNames = listApprovalToolNames(settings.tooling);
const workspace: WorkspaceState = {
...stored,
patterns: mergePatterns(stored.patterns ?? []).map((pattern) => ({
...pattern,
- approvalPolicy: pruneApprovalPolicyTools(
- normalizeApprovalPolicy(pattern.approvalPolicy),
- knownToolNames,
- ),
+ approvalPolicy: normalizeApprovalPolicy(pattern.approvalPolicy),
})),
projects,
sessions: (stored.sessions ?? []).map((session) => ({
...session,
runs: normalizeSessionRunRecords(session.runs),
tooling: normalizeSessionToolingSelection(session.tooling),
- approvalSettings: pruneSessionApprovalSettings(
- normalizeSessionApprovalSettings(session.approvalSettings),
- knownToolNames,
- ),
+ approvalSettings: normalizeSessionApprovalSettings(session.approvalSettings),
...normalizePendingApprovalState({
pendingApproval: session.pendingApproval,
pendingApprovalQueue: session.pendingApprovalQueue,
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 1c46e50..55647c0 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -275,6 +275,7 @@ export default function App() {
lspProfiles={workspace.settings.tooling.lspProfiles}
mcpServers={workspace.settings.tooling.mcpServers}
toolingSettings={workspace.settings.tooling}
+ runtimeTools={sidecarCapabilities?.runtimeTools}
onJumpToMessage={jumpToMessage}
onUpdateSessionTooling={(selection) => {
void api.updateSessionTooling({
diff --git a/src/renderer/components/ActivityPanel.tsx b/src/renderer/components/ActivityPanel.tsx
index e7d988d..6451cc3 100644
--- a/src/renderer/components/ActivityPanel.tsx
+++ b/src/renderer/components/ActivityPanel.tsx
@@ -19,6 +19,7 @@ import {
import type {
LspProfileDefinition,
McpServerDefinition,
+ RuntimeToolDefinition,
SessionToolingSelection,
WorkspaceToolingSettings,
} from '@shared/domain/tooling';
@@ -163,6 +164,7 @@ interface ActivityPanelProps {
lspProfiles: LspProfileDefinition[];
mcpServers: McpServerDefinition[];
toolingSettings: WorkspaceToolingSettings;
+ runtimeTools?: ReadonlyArray;
onJumpToMessage?: (messageId: string) => void;
onUpdateSessionTooling: (selection: SessionToolingSelection) => void;
onUpdateSessionApprovalSettings: (settings: { autoApprovedToolNames?: string[] }) => void;
@@ -176,6 +178,7 @@ export function ActivityPanel({
lspProfiles,
mcpServers,
toolingSettings,
+ runtimeTools,
onJumpToMessage,
onUpdateSessionTooling,
onUpdateSessionApprovalSettings,
@@ -188,7 +191,10 @@ export function ActivityPanel({
[activity, pattern.agents],
);
const selection = useMemo(() => resolveSessionToolingSelection(session), [session]);
- const approvalTools = useMemo(() => listApprovalToolDefinitions(toolingSettings), [toolingSettings]);
+ const approvalTools = useMemo(
+ () => listApprovalToolDefinitions(toolingSettings, runtimeTools),
+ [runtimeTools, toolingSettings],
+ );
const isOverridden = session.approvalSettings !== undefined;
const effectiveAutoApproved = new Set(
@@ -353,7 +359,7 @@ export function ActivityPanel({
) : approvalTools.length === 0 ? (
- Add MCP servers or LSP profiles in Settings to configure tool auto-approvals.
+ No approval-capable runtime tools are currently available.
- No tools available. Add MCP servers or LSP profiles in Settings to configure auto-approvals.
+ No approval-capable runtime tools are currently available.