Tools & Approvals
Tool Approval Queue
Triage pending tool calls by risk: select several, grant once, for the session or always, and undo from the resolved log.
2 variantsMIT
Preview
Permission requests
All clear
Nothing to review
New requests appear here.
Installation
Copies the component into components/ui and installs its dependencies and theme tokens.
pnpm dlx shadcn@latest add https://tuffui.com/r/tool-approval-queue.jsonUsage
Pass your own data and wire the callbacks to your agent runtime.
import { ToolApprovalQueue, type ToolApprovalRequest } from "@/components/ui/tool-approval-queue";
export function Permissions({ requests }: { requests: ToolApprovalRequest[] }) {
return (
<ToolApprovalQueue
data={requests}
layout="grouped"
onDecide={(picked, decision, scope) => {
for (const request of picked) {
agent.resolveToolCall(request.id, { approved: decision === "approved" });
if (decision === "approved" && scope !== "once") {
permissions.allow(request.tool, { scope }); // "session" | "always"
}
}
}}
onUndo={(request) => agent.reopenToolCall(request.id)}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data* | ToolApprovalRequest[] | — | Tool calls waiting on the user. Set a request's status to approved or denied to move it into the resolved log yourself. |
| layout | "grouped" | "flat" | "grouped" | Grouped sections requests by risk, high first; flat keeps arrival order. |
| onDecide | (requests, decision: "approved" | "denied", scope: GrantScope) => void | — | Called from a row (with the scope picked there) or from the bulk bar for every selected request (scope once). |
| onUndo | (request) => void | — | Called when the user undoes a decision from the resolved log. |
| undoMs | number | 5000 | How long Undo stays available after a decision. |
| selected | string[] | — | Controlled selection of request ids. Leave unset to let the queue own it. |
| onSelectedChange | (ids: string[]) => void | — | Called when rows are selected or cleared. |
| defaultExpanded | string | — | Id of a request to open on mount. |
| className | string | — | Extra classes for the root section. |
type GrantScope = "once" | "session" | "always";
type ToolApprovalRequest = {
id: string;
tool: string; // "bash", "fs.write", "slack.post"
title: string;
risk: "low" | "medium" | "high";
agent?: string;
status?: "pending" | "approved" | "denied";
scope?: GrantScope; // for requests you resolve yourself
payload:
| { kind: "shell"; command: string; cwd?: string }
| { kind: "diff"; path: string; lines: { type: "add" | "remove" | "context"; text: string }[] }
| { kind: "email"; to: string; subject: string; body: string }
| { kind: "json"; args: Record<string, unknown> };
};
// Keyboard: ↑/↓ move between rows, Space selects, Enter opens a row.