# Permission prompt

A tool call waiting for a person's go-ahead: what it will do, why it reached them, and the permission mode the run is in. They can edit the input before allowing it and see exactly what they changed; Allow then sends that input, nothing else. "Don't ask again" rules are offered as boxes nobody has ticked, and are kept only if the person ticks them. When the agent is asking questions instead, it shows them as a short form. Words from the request (its title, reason, input) are shown as text, never markup.

It sits in the thread, with no backdrop and nothing that dismisses it: the only ways out are Allow and Deny, and nothing else is ever counted as a denial. While the decision waits, its shield pulses slowly; a press on inert space outside it (the gesture that would dismiss a dialog) nudges it instead, so the person sees it is still waiting. Both stop once the answer is being sent.

- Registry name: `permission-prompt` (`@candor/permission-prompt`)
- Tier: Free (MIT). No licence key needed.
- Docs: https://candor.themethodic.com/docs/permission-prompt

## Install

```bash
npx candor-ui add permission-prompt
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/permission-prompt`.
It brings in: button, locale, payload, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { PermissionPrompt, type PermissionRequest } from "@/components/permission-prompt"

const command: PermissionRequest = {
  id: "p1",
  tool: "Bash",
  title: "The agent wants to run npm test -- invoices",
  reason: "No rule allows Bash commands yet.",
  input: { command: "npm test -- invoices", timeout: 120000 },
}

const question: PermissionRequest = {
  id: "p2",
  tool: "AskUserQuestion",
  input: {},
  questions: [
    {
      id: "Which test runner should the new tests use?",
      prompt: "Which test runner should the new tests use?",
      tag: "Runner",
      multiple: false,
      other: true,
      options: [
        { id: "Vitest", label: "Vitest", detail: "Already used by the web app" },
        { id: "Jest", label: "Jest", detail: "Used by the older packages" },
      ],
    },
  ],
}

export default function Example() {
  const [said, setSaid] = React.useState<string>()
  return (
    <div className="flex max-w-lg flex-col gap-6">
      <PermissionPrompt
        request={command}
        mode="default"
        editable
        suggestions={[{ action: "allow", what: "Bash(npm test:*)", scope: "local" }]}
        onAllow={(a) => setSaid(`Allowed${a.updatedInput ? " as edited" : ""}${a.alwaysAllow?.length ? ", and won’t ask again" : ""}.`)}
        onDeny={() => setSaid("Denied.")}
      />
      <PermissionPrompt request={question} onAllow={(a) => setSaid(`Answered: ${a.answers?.map((x) => [...(x.choice ?? []), x.text].filter(Boolean).join(", ")).join("; ")}`)} onDeny={() => setSaid("Not answered.")} />
      <p role="status" className="text-sm text-muted-foreground">{said}</p>
    </div>
  )
}
```

## <PermissionPrompt>

Props:

- `request: PermissionRequest`: The waiting request.
- `mode?: string | undefined`: The run's permission mode, as the SDK names it ("default", "acceptEdits", "plan", "auto"…). Left out, it isn't shown.
- `suggestions?: PermissionSuggestion[] | undefined`: "Don't ask again" rules to offer. None is ticked.
- `editable?: boolean | undefined` (default `false`): Let the person edit the input as JSON before allowing it.
- `onAllow: (answer: PermissionAnswer) => void`: Called with the edited input, the ticked rules or the answers.
- `onDeny: () => void`: Called when the person denies it (or won't answer the questions).
- `busy?: boolean | undefined` (default `false`): The answer is being sent: both buttons wait, and the waiting pulse stops.
- `className?: string | undefined`

## Strings (`permissionPrompt`)

The fixed words, English by default. Override any of them under `permissionPrompt` in `<LocaleProvider strings={{ permissionPrompt: { … } }}>`; the table is exported as `permissionPromptStrings`.

- `label`: Permission request: {title}
- `fallbackTitle`: The agent wants to use {tool}
- `why`: Why you’re asked
- `mode`: Permission mode
- `modeDefault`: Ask before acting
- `modeAcceptEdits`: Accept file edits
- `modePlan`: Plan only
- `modeAuto`: Automatic, by a classifier
- `modeDontAsk`: Don’t ask; deny what isn’t allowed
- `modeBypassPermissions`: Allow everything
- `blockedPath`: Path outside what’s allowed: {path}
- `subagent`: A subagent asked.
- `server`: From the tool server “{name}”, configured in {source}.
- `input`: What it will run with
- `edit`: Edit before allowing
- `editLabel`: Input, as JSON
- `editInvalid`: That isn’t a JSON object, so it can’t be used.
- `changed`: You changed
- `unchanged`: Nothing changed yet.
- `cancelEdit`: Use the original
- `remember`: Don’t ask again for
- `rememberHint`: Optional. Nothing is remembered unless you tick it.
- `ruleAllow`: Allow {what}
- `ruleDeny`: Always deny {what}
- `ruleAsk`: Always ask for {what}
- `ruleRemove`: Stop applying the rule for {what}
- `setMode`: Switch to: {mode}
- `warnBypassPermissions`: After this, nothing is checked: the agent can run any command and change or delete any file it can reach.
- `warnAuto`: After this, a classifier allows or blocks each action instead of you.
- `directories`: Access to {what}
- `other`: A permission change: {what}
- `scopeSession`: for this session
- `scopeLocal`: in this project, on this computer
- `scopeProject`: in this project, for everyone on it
- `scopeUser`: for you, in every project
- `allow`: Allow
- `allowEdited`: Allow as edited
- `deny`: Deny
- `answer`: Send answers
- `otherOption`: Other
- `otherLabel`: Your own answer to: {question}
- `questions`: The agent has questions

## Types

### PermissionSuggestion

One "don't ask again" choice, in the plain shape PermissionPrompt shows. claudeSuggestions builds them from the SDK's.

```ts
{ /** What the rule does. */ action: "allow" | "deny" | "ask" | "remove" | "mode" | "directories" | "other" /** What it covers, as the source writes it: `Bash(npm test:*)`, a mode's name, directories. Shown as code. */ what: string /** Where it's kept: "session", "local" (this project, this computer), "project" (shared), "user", or the source's own word. */ scope?: string | undefined }
```

### PermissionQuestion

A question the agent asks through the prompt: pick one or more labels, or answer in its own words.

```ts
{ id: string prompt: string tag?: string | undefined options: { id: string; label: string; detail?: string | undefined }[] multiple: boolean other?: boolean | undefined }
```

### PermissionRequest

A tool call waiting on a person, in the shape the adapters give (ClaudeApproval fits it).

```ts
{ id: string tool: string input: unknown /** The source's own sentence for the request: "Claude wants to run npm test". */ title?: string | undefined description?: string | undefined /** Why it reached a person (no rule allowed it, a safety check, a path outside the project). */ reason?: string | undefined blockedPath?: string | undefined /** Put the focus on Deny: the request grants more than it 
```

### PermissionAnswer

```ts
{ /** The input as edited, when the person changed it. */ updatedInput?: Record<string, unknown> | undefined /** The suggestions the person ticked, by index. */ alwaysAllow?: number[] | undefined /** Answers to `questions`. */ answers?: { questionId: string; choice?: string[] | undefined; text?: string | undefined }[] | undefined }
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
