diff --git a/webview-ui/src/components/chat/CommandExecution.tsx b/webview-ui/src/components/chat/CommandExecution.tsx
index 6424483383..dca2a9d923 100644
--- a/webview-ui/src/components/chat/CommandExecution.tsx
+++ b/webview-ui/src/components/chat/CommandExecution.tsx
@@ -26,7 +26,7 @@ interface CommandExecutionProps {
export const CommandExecution = ({ executionId, text, icon, title }: CommandExecutionProps) => {
const { t } = useAppTranslation()
- const { terminalShellIntegrationDisabled = false, allowedCommands = [] } = useExtensionState()
+ const { terminalShellIntegrationDisabled = false, allowedCommands = [], deniedCommands = [] } = useExtensionState()
const { command, output: parsedOutput, suggestions } = useMemo(() => parseCommandAndOutput(text), [text])
@@ -266,6 +266,30 @@ export const CommandExecution = ({ executionId, text, icon, title }: CommandExec
[allowedCommands],
)
+ const handleDenyPatternChange = useCallback(
+ (pattern: string) => {
+ if (!pattern) return
+
+ const isDenied = deniedCommands.includes(pattern)
+ let updatedDeniedCommands: string[]
+
+ if (isDenied) {
+ // Remove from deny list
+ updatedDeniedCommands = deniedCommands.filter((p) => p !== pattern)
+ } else {
+ // Add to deny list
+ updatedDeniedCommands = [...deniedCommands, pattern]
+ }
+
+ // Send message to update denied commands
+ vscode.postMessage({
+ type: "deniedCommands",
+ commands: updatedDeniedCommands,
+ })
+ },
+ [deniedCommands],
+ )
+
return (
{/* Header section */}
@@ -337,12 +361,14 @@ export const CommandExecution = ({ executionId, text, icon, title }: CommandExec
- {/* Whitelist section */}
+ {/* Command management section */}
{showSuggestions && (
)}
diff --git a/webview-ui/src/components/chat/CommandPatternSelector.tsx b/webview-ui/src/components/chat/CommandPatternSelector.tsx
index 706d43ea62..9c93ac6d21 100644
--- a/webview-ui/src/components/chat/CommandPatternSelector.tsx
+++ b/webview-ui/src/components/chat/CommandPatternSelector.tsx
@@ -1,6 +1,5 @@
import { useState } from "react"
-import { ChevronDown } from "lucide-react"
-import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
+import { ChevronDown, Check, X } from "lucide-react"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { cn } from "@src/lib/utils"
@@ -12,10 +11,18 @@ interface CommandPattern {
interface CommandPatternSelectorProps {
patterns: CommandPattern[]
allowedCommands: string[]
- onPatternChange: (pattern: string) => void
+ deniedCommands: string[]
+ onAllowPatternChange: (pattern: string) => void
+ onDenyPatternChange: (pattern: string) => void
}
-export const CommandPatternSelector = ({ patterns, allowedCommands, onPatternChange }: CommandPatternSelectorProps) => {
+export const CommandPatternSelector = ({
+ patterns,
+ allowedCommands,
+ deniedCommands,
+ onAllowPatternChange,
+ onDenyPatternChange,
+}: CommandPatternSelectorProps) => {
const { t } = useAppTranslation()
const [isExpanded, setIsExpanded] = useState(false)
@@ -23,12 +30,38 @@ export const CommandPatternSelector = ({ patterns, allowedCommands, onPatternCha
return null
}
+ const getPatternStatus = (pattern: string): "allowed" | "denied" | "none" => {
+ if (allowedCommands.includes(pattern)) return "allowed"
+ if (deniedCommands.includes(pattern)) return "denied"
+ return "none"
+ }
+
+ const handleAllowClick = (pattern: string) => {
+ const status = getPatternStatus(pattern)
+ if (status === "denied") {
+ // Remove from denied list first
+ onDenyPatternChange(pattern)
+ }
+ // Toggle allow status
+ onAllowPatternChange(pattern)
+ }
+
+ const handleDenyClick = (pattern: string) => {
+ const status = getPatternStatus(pattern)
+ if (status === "allowed") {
+ // Remove from allowed list first
+ onAllowPatternChange(pattern)
+ }
+ // Toggle deny status
+ onDenyPatternChange(pattern)
+ }
+
return (
{isExpanded && (
-
- {patterns.map((item, index) => (
-
- onPatternChange(item.pattern)}
- className="text-xs"
- aria-label={`Allow command pattern: ${item.pattern}`}>
- {item.pattern}
-
-
- ))}
+
+
+ {t("chat:commandExecution.commandManagementDescription")}
+
+ {patterns.map((item, index) => {
+ const status = getPatternStatus(item.pattern)
+ return (
+
+
+ {item.pattern}
+ {item.description && (
+
+ - {item.description}
+
+ )}
+
+
+
+
+
+
+ )
+ })}
)}
diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json
index f2b41a6d9c..77c1191bee 100644
--- a/webview-ui/src/i18n/locales/en/chat.json
+++ b/webview-ui/src/i18n/locales/en/chat.json
@@ -213,7 +213,9 @@
"exited": "Exited ({{exitCode}})",
"addToAllowedCommands": "Add to Allowed Auto-Execute Commands",
"allowAllNpmRun": "Allow all npm run commands",
- "allowAllNpm": "Allow all npm commands"
+ "allowAllNpm": "Allow all npm commands",
+ "manageCommands": "Manage Command Permissions",
+ "commandManagementDescription": "Click ✓ to allow auto-execution, ✗ to deny execution"
},
"commandOutput": "Command Output",
"response": "Response",