feat: shell command explanation option (#173)

# Shell Command Explanation Option

## Description
This PR adds an option to explain shell commands when the user is
prompted to approve them (Fixes #110). When reviewing a shell command,
users can now select "Explain this command" to get a detailed
explanation of what the command does before deciding whether to approve
or reject it.

## Changes
- Added a new "EXPLAIN" option to the `ReviewDecision` enum
- Updated the command review UI to include an "Explain this command (x)"
option
- Implemented the logic to send the command to the LLM for explanation
using the same model as the agent
- Added a display for the explanation in the command review UI
- Updated all relevant components to pass the explanation through the
component tree

## Benefits
- Improves user understanding of shell commands before approving them
- Reduces the risk of approving potentially harmful commands
- Enhances the educational aspect of the tool, helping users learn about
shell commands
- Maintains the same workflow with minimal UI changes

## Testing
- Manually tested the explanation feature with various shell commands
- Verified that the explanation is displayed correctly in the UI
- Confirmed that the user can still approve or reject the command after
viewing the explanation

## Screenshots

![improved_shell_explanation_demo](https://github.com/user-attachments/assets/05923481-29db-4eba-9cc6-5e92301d2be0)


## Additional Notes
The explanation is generated using the same model as the agent, ensuring
consistency in the quality and style of explanations.

---------

Signed-off-by: crazywolf132 <crazywolf132@gmail.com>
This commit is contained in:
Brayden Moon
2025-04-18 06:28:58 +10:00
committed by GitHub
parent 693a6f96cf
commit f3d085aaf8
11 changed files with 352 additions and 68 deletions

View File

@@ -12,12 +12,17 @@ type ConfirmationResult = {
type ConfirmationItem = {
prompt: React.ReactNode;
resolve: (result: ConfirmationResult) => void;
explanation?: string;
};
export function useConfirmation(): {
submitConfirmation: (result: ConfirmationResult) => void;
requestConfirmation: (prompt: React.ReactNode) => Promise<ConfirmationResult>;
requestConfirmation: (
prompt: React.ReactNode,
explanation?: string,
) => Promise<ConfirmationResult>;
confirmationPrompt: React.ReactNode | null;
explanation?: string;
} {
// The current prompt is just the head of the queue
const [current, setCurrent] = useState<ConfirmationItem | null>(null);
@@ -32,10 +37,10 @@ export function useConfirmation(): {
// Called whenever someone wants a confirmation
const requestConfirmation = useCallback(
(prompt: React.ReactNode) => {
(prompt: React.ReactNode, explanation?: string) => {
return new Promise<ConfirmationResult>((resolve) => {
const wasEmpty = queueRef.current.length === 0;
queueRef.current.push({ prompt, resolve });
queueRef.current.push({ prompt, resolve, explanation });
// If the queue was empty, we need to kick off the first prompt
if (wasEmpty) {
@@ -56,6 +61,7 @@ export function useConfirmation(): {
return {
confirmationPrompt: current?.prompt, // the prompt to render now
explanation: current?.explanation, // the explanation to render if available
requestConfirmation,
submitConfirmation,
};