Skip to content

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.json

Usage

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

PropTypeDefaultDescription
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.
undoMsnumber5000How long Undo stays available after a decision.
selectedstring[]—Controlled selection of request ids. Leave unset to let the queue own it.
onSelectedChange(ids: string[]) => void—Called when rows are selected or cleared.
defaultExpandedstring—Id of a request to open on mount.
classNamestring—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.