mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
feat: Add auto-approval for mode switching
Implements automatic approval for mode switching operations when enabled, following existing auto-approval patterns in the codebase. Implementation: - Added `alwaysAllowModeSwitch` to state management - Updated `isAutoApproved` function in ChatView to handle mode switch requests - Added mode switch option to AutoApproveMenu with appropriate handler - Integrated with existing auto-approval flow Tests: - Added three test cases in ChatView.auto-approve.test.tsx: 1. Verifies mode switch auto-approval when enabled 2. Verifies no auto-approval when mode switch setting is disabled 3. Verifies no auto-approval when global auto-approval is disabled The implementation follows existing patterns for other auto-approve features (read, write, browser, etc.) to maintain consistency in the codebase.
This commit is contained in:
@@ -28,6 +28,8 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
setAlwaysAllowBrowser,
|
||||
alwaysAllowMcp,
|
||||
setAlwaysAllowMcp,
|
||||
alwaysAllowModeSwitch,
|
||||
setAlwaysAllowModeSwitch,
|
||||
alwaysApproveResubmit,
|
||||
setAlwaysApproveResubmit,
|
||||
autoApprovalEnabled,
|
||||
@@ -71,6 +73,13 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
enabled: alwaysAllowMcp ?? false,
|
||||
description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.",
|
||||
},
|
||||
{
|
||||
id: "switchModes",
|
||||
label: "Switch between modes",
|
||||
shortName: "Modes",
|
||||
enabled: alwaysAllowModeSwitch ?? false,
|
||||
description: "Allows automatic switching between different AI modes without requiring approval.",
|
||||
},
|
||||
{
|
||||
id: "retryRequests",
|
||||
label: "Retry failed requests",
|
||||
@@ -120,6 +129,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
vscode.postMessage({ type: "alwaysAllowMcp", bool: newValue })
|
||||
}, [alwaysAllowMcp, setAlwaysAllowMcp])
|
||||
|
||||
const handleModeSwitchChange = useCallback(() => {
|
||||
const newValue = !(alwaysAllowModeSwitch ?? false)
|
||||
setAlwaysAllowModeSwitch(newValue)
|
||||
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue })
|
||||
}, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch])
|
||||
|
||||
const handleRetryChange = useCallback(() => {
|
||||
const newValue = !(alwaysApproveResubmit ?? false)
|
||||
setAlwaysApproveResubmit(newValue)
|
||||
@@ -133,6 +148,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
executeCommands: handleExecuteChange,
|
||||
useBrowser: handleBrowserChange,
|
||||
useMcp: handleMcpChange,
|
||||
switchModes: handleModeSwitchChange,
|
||||
retryRequests: handleRetryChange,
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
mode,
|
||||
setMode,
|
||||
autoApprovalEnabled,
|
||||
alwaysAllowModeSwitch,
|
||||
} = useExtensionState()
|
||||
|
||||
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
|
||||
@@ -565,7 +566,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
(alwaysAllowReadOnly && message.ask === "tool" && isReadOnlyToolAction(message)) ||
|
||||
(alwaysAllowWrite && message.ask === "tool" && isWriteToolAction(message)) ||
|
||||
(alwaysAllowExecute && message.ask === "command" && isAllowedCommand(message)) ||
|
||||
(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message))
|
||||
(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message)) ||
|
||||
(alwaysAllowModeSwitch &&
|
||||
message.ask === "tool" &&
|
||||
JSON.parse(message.text || "{}")?.tool === "switchMode")
|
||||
)
|
||||
},
|
||||
[
|
||||
@@ -579,6 +583,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
isAllowedCommand,
|
||||
alwaysAllowMcp,
|
||||
isMcpToolAlwaysAllowed,
|
||||
alwaysAllowModeSwitch,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -313,4 +313,168 @@ describe("ChatView - Auto Approval Tests", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("auto-approves mode switch when enabled", async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>,
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
alwaysAllowModeSwitch: true,
|
||||
autoApprovalEnabled: true,
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Then send the mode switch ask message
|
||||
mockPostMessage({
|
||||
alwaysAllowModeSwitch: true,
|
||||
autoApprovalEnabled: true,
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
ts: Date.now(),
|
||||
text: JSON.stringify({ tool: "switchMode" }),
|
||||
partial: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Wait for the auto-approval message
|
||||
await waitFor(() => {
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "askResponse",
|
||||
askResponse: "yesButtonClicked",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("does not auto-approve mode switch when disabled", async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>,
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
alwaysAllowModeSwitch: false,
|
||||
autoApprovalEnabled: true,
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Then send the mode switch ask message
|
||||
mockPostMessage({
|
||||
alwaysAllowModeSwitch: false,
|
||||
autoApprovalEnabled: true,
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
ts: Date.now(),
|
||||
text: JSON.stringify({ tool: "switchMode" }),
|
||||
partial: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Verify no auto-approval message was sent
|
||||
expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
||||
type: "askResponse",
|
||||
askResponse: "yesButtonClicked",
|
||||
})
|
||||
})
|
||||
|
||||
it("does not auto-approve mode switch when auto-approval is disabled", async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>,
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
alwaysAllowModeSwitch: true,
|
||||
autoApprovalEnabled: false,
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Then send the mode switch ask message
|
||||
mockPostMessage({
|
||||
alwaysAllowModeSwitch: true,
|
||||
autoApprovalEnabled: false,
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
ts: Date.now(),
|
||||
text: JSON.stringify({ tool: "switchMode" }),
|
||||
partial: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Verify no auto-approval message was sent
|
||||
expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
||||
type: "askResponse",
|
||||
askResponse: "yesButtonClicked",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
||||
setAlwaysAllowExecute: (value: boolean) => void
|
||||
setAlwaysAllowBrowser: (value: boolean) => void
|
||||
setAlwaysAllowMcp: (value: boolean) => void
|
||||
setAlwaysAllowModeSwitch: (value: boolean) => void
|
||||
setShowAnnouncement: (value: boolean) => void
|
||||
setAllowedCommands: (value: string[]) => void
|
||||
setSoundEnabled: (value: boolean) => void
|
||||
@@ -253,6 +254,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
setAlwaysAllowExecute: (value) => setState((prevState) => ({ ...prevState, alwaysAllowExecute: value })),
|
||||
setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
|
||||
setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })),
|
||||
setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })),
|
||||
setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
|
||||
setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
|
||||
setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
|
||||
|
||||
Reference in New Issue
Block a user