In the previous chapter, the Unified Dialog Interface, we learned how to draw a consistent "frame" around our permission requests. We made the UI look professional with borders, titles, and colors.
However, a pretty box is useless if the user can't interact with it. A contract isn't valid until someone signs it.
This chapter introduces the Interactive Decision Prompt. This is the component responsible for handling the "Yes," "No," and "Always Allow" buttons, managing keyboard navigation, and capturing optional text feedback (like "No, do this instead").
Imagine you are at a checkout terminal in a store. You swipe your card, and the screen asks: "Is the amount $50.00 correct?"
You have two physical buttons:
Sometimes, if you press "No," a keyboard pops up asking "Why?" or "Enter Correct Amount."
In our project, the Interactive Decision Prompt (PermissionPrompt) is that terminal.
Why can't we just let the user type y or n in the console?
yarn instead of npm."
The Scenario:
The AI wants to run npm install lodash.
The Interaction:
> Allow (Selected) RejectReject.To make this work, the component handles three main jobs:
This is the data structure defining what choices the user has. It usually includes:
'allow', 'block').This is a "state" (a switch) inside the component.
When the user hits "Enter", the component calls a function (onSelect) to tell the main application what happened.
The PermissionPrompt component is designed to be plugged into any request. Here is how we use it conceptually.
// Inside a specific tool request (e.g., BashPermissionRequest)
return (
<PermissionPrompt
question="Do you want to run this command?"
options={[
{ value: 'allow', label: 'Allow' },
{ value: 'reject', label: 'Reject' }
]}
onSelect={(decision, feedback) => {
// Send the result back to the system
handleDecision(decision, feedback);
}}
/>
);
options: We pass the list of choices.onSelect: This function runs when the user makes a final choice. It receives the decision (e.g., 'reject') and any feedback (e.g., "Use pnpm").Let's visualize the flow of data when a user interacts with the prompt.
Let's look at PermissionPrompt.tsx to see how it manages this logic.
The component needs to remember what is happening right now.
// PermissionPrompt.tsx
export function PermissionPrompt({ options, onSelect }) {
// 1. Store the text the user types
const [acceptFeedback, setAcceptFeedback] = useState('');
const [rejectFeedback, setRejectFeedback] = useState('');
// 2. Store if the user is currently typing
const [acceptInputMode, setAcceptInputMode] = useState(false);
// ... render logic
}
We use a custom hook useKeybindings (which we will assume handles the raw key events) to map keys to actions.
// PermissionPrompt.tsx (Simplified)
const keybindingHandlers = {
// When 'Enter' is pressed on a specific option
'Select': () => handleSelect(focusedValue),
// When 'Esc' is pressed
'Cancel': () => onCancel(),
};
useKeybindings(keybindingHandlers);
When the user makes a choice, we need to bundle the decision type (Accept/Reject) with their text message.
// PermissionPrompt.tsx
const handleSelect = (value) => {
// Find which option was selected
const option = options.find(opt => opt.value === value);
// specific logic to grab the correct text buffer
let feedback = undefined;
if (option.type === 'accept') feedback = acceptFeedback;
if (option.type === 'reject') feedback = rejectFeedback;
// Finalize the decision
onSelect(value, feedback);
};
The actual visual list is delegated to a sub-component called Select. This keeps the main logic clean.
// PermissionPrompt.tsx
return (
<Box flexDirection="column">
<Text>{question}</Text>
<Select
options={options}
onChange={handleSelect}
// Pass the feedback state down so the inputs work
onInputModeToggle={handleInputModeToggle}
/>
<Text dimColor>Esc to cancel</Text>
</Box>
);
You might notice imports from hooks.ts or analytics. The PermissionPrompt isn't just a dumb UI; it's also responsible for recording how the user decided.
When handleSelect is called, it triggers logEvent. This helps developers understand:
(This logic is encapsulated in hooks.ts to keep the UI component cleaner).
The Interactive Decision Prompt is the bridge between the AI's request and the user's authority. It transforms a static notification into a two-way conversation, allowing the user to guide the AI with feedback rather than just blocking it.
So far, we have:
But what happens after the user says "Yes"? If the request was to edit a file, who actually writes to the disk?
In the next chapter, we dive into the engine room to see how file changes are actually executed.
Next Chapter: File Operations Subsystem
Generated by Code IQ