mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-06 19:58:43 +02:00
feat: add 'Always approve' button for tool-call approvals
When a tool-call approval banner appears, users can now click 'Always approve' to both approve the current call AND add the tool to the session's autoApprovedToolNames. Future calls to that tool are then auto-approved without showing the banner. - Add alwaysApprove?: boolean to ResolveSessionApprovalInput - In resolveSessionApproval, atomically append tool to session approvalSettings when alwaysApprove is true (avoids running guard) - Add 'Always approve' button in ApprovalBanner (tool-call kind only) - Wire through ChatPane and App.tsx with updated callback signatures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -39,7 +39,7 @@ interface ChatPaneProps {
|
||||
runtimeTools?: ReadonlyArray<RuntimeToolDefinition>;
|
||||
onSend: (content: string) => Promise<void>;
|
||||
onCancelTurn?: () => void;
|
||||
onResolveApproval?: (approvalId: string, decision: ApprovalDecision) => Promise<unknown>;
|
||||
onResolveApproval?: (approvalId: string, decision: ApprovalDecision, alwaysApprove?: boolean) => Promise<unknown>;
|
||||
onResolveUserInput?: (userInputId: string, answer: string, wasFreeform: boolean) => Promise<unknown>;
|
||||
onSetInteractionMode?: (mode: InteractionMode) => void;
|
||||
onDismissPlanReview?: () => void;
|
||||
@@ -178,14 +178,14 @@ export function ChatPane({
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResolveApproval(decision: ApprovalDecision) {
|
||||
async function handleResolveApproval(decision: ApprovalDecision, alwaysApprove?: boolean) {
|
||||
if (!pendingApproval || !onResolveApproval || isResolvingApproval) return;
|
||||
|
||||
setApprovalError(undefined);
|
||||
setIsResolvingApproval(true);
|
||||
|
||||
try {
|
||||
await onResolveApproval(pendingApproval.id, decision);
|
||||
await onResolveApproval(pendingApproval.id, decision, alwaysApprove);
|
||||
} catch (error) {
|
||||
setApprovalError(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
@@ -384,7 +384,7 @@ export function ChatPane({
|
||||
<ApprovalBanner
|
||||
approval={pendingApproval}
|
||||
isResolving={isResolvingApproval}
|
||||
onResolve={(decision) => void handleResolveApproval(decision)}
|
||||
onResolve={(decision, alwaysApprove) => void handleResolveApproval(decision, alwaysApprove)}
|
||||
position={totalPendingCount > 1 ? 1 : undefined}
|
||||
total={totalPendingCount > 1 ? totalPendingCount : undefined}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Bot, Check, ChevronDown, Loader2, ShieldAlert, ShieldCheck, X } from 'lucide-react';
|
||||
import { Bot, Check, ChevronDown, Loader2, ShieldAlert, ShieldBan, ShieldCheck, X } from 'lucide-react';
|
||||
|
||||
import { MarkdownContent } from '@renderer/components/MarkdownContent';
|
||||
import { permissionDetailSummary, PermissionDetailView } from '@renderer/components/chat/PermissionDetailView';
|
||||
@@ -15,7 +15,7 @@ export function ApprovalBanner({
|
||||
total,
|
||||
}: {
|
||||
approval: PendingApprovalRecord;
|
||||
onResolve: (decision: ApprovalDecision) => void;
|
||||
onResolve: (decision: ApprovalDecision, alwaysApprove?: boolean) => void;
|
||||
isResolving: boolean;
|
||||
position?: number;
|
||||
total?: number;
|
||||
@@ -23,6 +23,7 @@ export function ApprovalBanner({
|
||||
const kindLabel = approval.kind === 'final-response' ? 'Final response review' : 'Tool call approval';
|
||||
const hasMessages = approval.messages && approval.messages.length > 0;
|
||||
const showPosition = position !== undefined && total !== undefined && total > 1;
|
||||
const canAlwaysApprove = approval.kind === 'tool-call' && !!approval.toolName;
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-amber-500/30 bg-amber-500/5 px-4 py-3" role="alert">
|
||||
@@ -87,6 +88,19 @@ export function ApprovalBanner({
|
||||
{isResolving ? <Loader2 className="size-3 animate-spin" /> : <Check className="size-3" />}
|
||||
Approve
|
||||
</button>
|
||||
{canAlwaysApprove && (
|
||||
<button
|
||||
aria-label={`Always approve ${approval.toolName}`}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600/20 px-3.5 py-1.5 text-[12px] font-medium text-emerald-300 transition hover:bg-emerald-600/30 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={isResolving}
|
||||
onClick={() => onResolve('approved', true)}
|
||||
title={`Auto-approve "${approval.toolName}" for the rest of this session`}
|
||||
type="button"
|
||||
>
|
||||
<ShieldBan className="size-3" />
|
||||
Always approve
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-zinc-800 px-3.5 py-1.5 text-[12px] font-medium text-zinc-300 transition hover:bg-zinc-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={isResolving}
|
||||
|
||||
Reference in New Issue
Block a user