mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Customize for Roo
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useCallback, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
|
||||
interface AutoApproveAction {
|
||||
id: string
|
||||
label: string
|
||||
enabled: boolean
|
||||
shortName: string
|
||||
description: string
|
||||
}
|
||||
|
||||
@@ -13,58 +14,97 @@ interface AutoApproveMenuProps {
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_REQUESTS = 50
|
||||
|
||||
const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [actions, setActions] = useState<AutoApproveAction[]>([
|
||||
const {
|
||||
alwaysAllowReadOnly,
|
||||
setAlwaysAllowReadOnly,
|
||||
alwaysAllowWrite,
|
||||
setAlwaysAllowWrite,
|
||||
alwaysAllowExecute,
|
||||
setAlwaysAllowExecute,
|
||||
alwaysAllowBrowser,
|
||||
setAlwaysAllowBrowser,
|
||||
alwaysAllowMcp,
|
||||
setAlwaysAllowMcp,
|
||||
alwaysApproveResubmit,
|
||||
setAlwaysApproveResubmit,
|
||||
autoApprovalEnabled,
|
||||
setAutoApprovalEnabled,
|
||||
} = useExtensionState()
|
||||
|
||||
const actions: AutoApproveAction[] = [
|
||||
{
|
||||
id: "readFiles",
|
||||
label: "Read files and directories",
|
||||
enabled: false,
|
||||
shortName: "Read",
|
||||
enabled: alwaysAllowReadOnly ?? false,
|
||||
description: "Allows access to read any file on your computer.",
|
||||
},
|
||||
{
|
||||
id: "editFiles",
|
||||
label: "Edit files",
|
||||
enabled: false,
|
||||
shortName: "Edit",
|
||||
enabled: alwaysAllowWrite ?? false,
|
||||
description: "Allows modification of any files on your computer.",
|
||||
},
|
||||
{
|
||||
id: "executeCommands",
|
||||
label: "Execute safe commands",
|
||||
enabled: false,
|
||||
shortName: "Commands",
|
||||
enabled: alwaysAllowExecute ?? false,
|
||||
description:
|
||||
"Allows automatic execution of safe terminal commands. The model will determine if a command is potentially destructive and ask for explicit approval.",
|
||||
"Allows execution of approved terminal commands. You can configure this in the settings panel.",
|
||||
},
|
||||
{
|
||||
id: "useBrowser",
|
||||
label: "Use the browser",
|
||||
enabled: false,
|
||||
shortName: "Browser",
|
||||
enabled: alwaysAllowBrowser ?? false,
|
||||
description: "Allows ability to launch and interact with any website in a headless browser.",
|
||||
},
|
||||
{
|
||||
id: "useMcp",
|
||||
label: "Use MCP servers",
|
||||
enabled: false,
|
||||
shortName: "MCP",
|
||||
enabled: alwaysAllowMcp ?? false,
|
||||
description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.",
|
||||
},
|
||||
])
|
||||
const [maxRequests, setMaxRequests] = useState(DEFAULT_MAX_REQUESTS)
|
||||
const [enableNotifications, setEnableNotifications] = useState(false)
|
||||
{
|
||||
id: "retryRequests",
|
||||
label: "Retry failed requests",
|
||||
shortName: "Retries",
|
||||
enabled: alwaysApproveResubmit ?? false,
|
||||
description: "Automatically retry failed API requests when the provider returns an error response.",
|
||||
},
|
||||
]
|
||||
|
||||
const toggleExpanded = useCallback(() => {
|
||||
setIsExpanded((prev) => !prev)
|
||||
}, [])
|
||||
|
||||
const toggleAction = useCallback((actionId: string) => {
|
||||
setActions((prev) =>
|
||||
prev.map((action) => (action.id === actionId ? { ...action, enabled: !action.enabled } : action)),
|
||||
)
|
||||
}, [])
|
||||
const enabledActionsList = actions
|
||||
.filter((action) => action.enabled)
|
||||
.map((action) => action.shortName)
|
||||
.join(", ")
|
||||
|
||||
const enabledActions = actions.filter((action) => action.enabled)
|
||||
const enabledActionsList = enabledActions.map((action) => action.label).join(", ")
|
||||
// Individual checkbox handlers - each one only updates its own state
|
||||
const handleReadOnlyChange = useCallback(() => setAlwaysAllowReadOnly(!(alwaysAllowReadOnly ?? false)), [alwaysAllowReadOnly, setAlwaysAllowReadOnly])
|
||||
const handleWriteChange = useCallback(() => setAlwaysAllowWrite(!(alwaysAllowWrite ?? false)), [alwaysAllowWrite, setAlwaysAllowWrite])
|
||||
const handleExecuteChange = useCallback(() => setAlwaysAllowExecute(!(alwaysAllowExecute ?? false)), [alwaysAllowExecute, setAlwaysAllowExecute])
|
||||
const handleBrowserChange = useCallback(() => setAlwaysAllowBrowser(!(alwaysAllowBrowser ?? false)), [alwaysAllowBrowser, setAlwaysAllowBrowser])
|
||||
const handleMcpChange = useCallback(() => setAlwaysAllowMcp(!(alwaysAllowMcp ?? false)), [alwaysAllowMcp, setAlwaysAllowMcp])
|
||||
const handleRetryChange = useCallback(() => setAlwaysApproveResubmit(!(alwaysApproveResubmit ?? false)), [alwaysApproveResubmit, setAlwaysApproveResubmit])
|
||||
|
||||
// Map action IDs to their specific handlers
|
||||
const actionHandlers: Record<AutoApproveAction['id'], () => void> = {
|
||||
readFiles: handleReadOnlyChange,
|
||||
editFiles: handleWriteChange,
|
||||
executeCommands: handleExecuteChange,
|
||||
useBrowser: handleBrowserChange,
|
||||
useMcp: handleMcpChange,
|
||||
retryRequests: handleRetryChange,
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -86,39 +126,41 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={toggleExpanded}>
|
||||
<VSCodeCheckbox
|
||||
checked={enabledActions.length > 0}
|
||||
onChange={(e) => {
|
||||
const checked = (e.target as HTMLInputElement).checked
|
||||
setActions((prev) =>
|
||||
prev.map((action) => ({
|
||||
...action,
|
||||
enabled: checked,
|
||||
})),
|
||||
)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<CollapsibleSection>
|
||||
<span style={{ color: "var(--vscode-foreground)" }}>Auto-approve:</span>
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}>
|
||||
{enabledActions.length === 0 ? "None" : enabledActionsList}
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<VSCodeCheckbox
|
||||
checked={autoApprovalEnabled ?? false}
|
||||
onChange={() => setAutoApprovalEnabled(!(autoApprovalEnabled ?? false))}
|
||||
/>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
flex: 1,
|
||||
minWidth: 0
|
||||
}}>
|
||||
<span style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
flexShrink: 0
|
||||
}}>Auto-approve:</span>
|
||||
<span style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
flex: 1,
|
||||
minWidth: 0
|
||||
}}>
|
||||
{enabledActionsList || "None"}
|
||||
</span>
|
||||
<span
|
||||
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
|
||||
style={{
|
||||
// fontSize: "14px",
|
||||
flexShrink: 0,
|
||||
marginLeft: isExpanded ? "2px" : "-2px",
|
||||
}}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<div style={{ padding: "0" }}>
|
||||
@@ -129,13 +171,17 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
Auto-approve allows Cline to perform actions without asking for permission. Only enable for
|
||||
actions you fully trust, and consider setting a low request limit as a safeguard.
|
||||
actions you fully trust.
|
||||
</div>
|
||||
{actions.map((action) => (
|
||||
<div key={action.id} style={{ margin: "6px 0" }}>
|
||||
<VSCodeCheckbox checked={action.enabled} onChange={() => toggleAction(action.id)}>
|
||||
{action.label}
|
||||
</VSCodeCheckbox>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<VSCodeCheckbox
|
||||
checked={action.enabled}
|
||||
onChange={actionHandlers[action.id]}>
|
||||
{action.label}
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
marginLeft: "28px",
|
||||
@@ -146,76 +192,10 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
height: "0.5px",
|
||||
background: "var(--vscode-titleBar-inactiveForeground)",
|
||||
margin: "15px 0",
|
||||
opacity: 0.2,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
marginTop: "10px",
|
||||
marginBottom: "8px",
|
||||
color: "var(--vscode-foreground)",
|
||||
}}>
|
||||
<span style={{ flexShrink: 1, minWidth: 0 }}>Max Requests:</span>
|
||||
<VSCodeTextField
|
||||
value={maxRequests.toString()}
|
||||
onChange={(e) => {
|
||||
const value = parseInt((e.target as HTMLInputElement).value)
|
||||
if (!isNaN(value) && value > 0) {
|
||||
setMaxRequests(value)
|
||||
}
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "12px",
|
||||
marginBottom: "10px",
|
||||
}}>
|
||||
Cline will make this many API requests before asking for approval to proceed with the task.
|
||||
</div>
|
||||
<div style={{ margin: "6px 0" }}>
|
||||
<VSCodeCheckbox
|
||||
checked={enableNotifications}
|
||||
onChange={() => setEnableNotifications((prev) => !prev)}>
|
||||
Enable Notifications
|
||||
</VSCodeCheckbox>
|
||||
<div
|
||||
style={{
|
||||
marginLeft: "28px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
Receive system notifications when Cline requires approval to proceed or when a task is
|
||||
completed.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CollapsibleSection = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
`
|
||||
|
||||
export default AutoApproveMenu
|
||||
|
||||
@@ -527,7 +527,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
minHeight: "100px",
|
||||
margin: "10px 15px",
|
||||
padding: "8px"
|
||||
}}
|
||||
@@ -652,7 +651,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
onHeightChange?.(height)
|
||||
}}
|
||||
placeholder={placeholderText}
|
||||
minRows={4}
|
||||
minRows={2}
|
||||
maxRows={20}
|
||||
autoFocus={true}
|
||||
style={{
|
||||
|
||||
@@ -39,7 +39,7 @@ interface ChatViewProps {
|
||||
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
||||
|
||||
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
|
||||
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands, writeDelayMs, mode, setMode } = useExtensionState()
|
||||
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands, writeDelayMs, mode, setMode, autoApprovalEnabled } = useExtensionState()
|
||||
|
||||
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
|
||||
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
|
||||
@@ -529,7 +529,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
|
||||
const isAutoApproved = useCallback(
|
||||
(message: ClineMessage | undefined) => {
|
||||
if (!message || message.type !== "ask") return false
|
||||
if (!autoApprovalEnabled || !message || message.type !== "ask") return false
|
||||
|
||||
return (
|
||||
(alwaysAllowBrowser && message.ask === "browser_action_launch") ||
|
||||
@@ -539,17 +539,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message))
|
||||
)
|
||||
},
|
||||
[
|
||||
alwaysAllowBrowser,
|
||||
alwaysAllowReadOnly,
|
||||
alwaysAllowWrite,
|
||||
alwaysAllowExecute,
|
||||
alwaysAllowMcp,
|
||||
isReadOnlyToolAction,
|
||||
isWriteToolAction,
|
||||
isAllowedCommand,
|
||||
isMcpToolAlwaysAllowed
|
||||
]
|
||||
[autoApprovalEnabled, alwaysAllowBrowser, alwaysAllowReadOnly, isReadOnlyToolAction, alwaysAllowWrite, isWriteToolAction, alwaysAllowExecute, isAllowedCommand, alwaysAllowMcp, isMcpToolAlwaysAllowed]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import { render, fireEvent, screen } from "@testing-library/react"
|
||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
||||
import AutoApproveMenu from "../AutoApproveMenu"
|
||||
import { codeMode, defaultPrompts } from "../../../../../src/shared/modes"
|
||||
|
||||
// Mock the ExtensionStateContext hook
|
||||
jest.mock("../../../context/ExtensionStateContext")
|
||||
|
||||
const mockUseExtensionState = useExtensionState as jest.MockedFunction<typeof useExtensionState>
|
||||
|
||||
describe("AutoApproveMenu", () => {
|
||||
const defaultMockState = {
|
||||
// Required state properties
|
||||
version: "1.0.0",
|
||||
clineMessages: [],
|
||||
taskHistory: [],
|
||||
shouldShowAnnouncement: false,
|
||||
allowedCommands: [],
|
||||
soundEnabled: false,
|
||||
soundVolume: 0.5,
|
||||
diffEnabled: false,
|
||||
fuzzyMatchThreshold: 1.0,
|
||||
preferredLanguage: "English",
|
||||
writeDelayMs: 1000,
|
||||
browserViewportSize: "900x600",
|
||||
screenshotQuality: 75,
|
||||
terminalOutputLineLimit: 500,
|
||||
mcpEnabled: true,
|
||||
requestDelaySeconds: 5,
|
||||
currentApiConfigName: "default",
|
||||
listApiConfigMeta: [],
|
||||
mode: codeMode,
|
||||
customPrompts: defaultPrompts,
|
||||
enhancementApiConfigId: "",
|
||||
didHydrateState: true,
|
||||
showWelcome: false,
|
||||
theme: {},
|
||||
glamaModels: {},
|
||||
openRouterModels: {},
|
||||
openAiModels: [],
|
||||
mcpServers: [],
|
||||
filePaths: [],
|
||||
|
||||
// Auto-approve specific properties
|
||||
alwaysAllowReadOnly: false,
|
||||
alwaysAllowWrite: false,
|
||||
alwaysAllowExecute: false,
|
||||
alwaysAllowBrowser: false,
|
||||
alwaysAllowMcp: false,
|
||||
alwaysApproveResubmit: false,
|
||||
autoApprovalEnabled: false,
|
||||
|
||||
// Required setter functions
|
||||
setApiConfiguration: jest.fn(),
|
||||
setCustomInstructions: jest.fn(),
|
||||
setAlwaysAllowReadOnly: jest.fn(),
|
||||
setAlwaysAllowWrite: jest.fn(),
|
||||
setAlwaysAllowExecute: jest.fn(),
|
||||
setAlwaysAllowBrowser: jest.fn(),
|
||||
setAlwaysAllowMcp: jest.fn(),
|
||||
setShowAnnouncement: jest.fn(),
|
||||
setAllowedCommands: jest.fn(),
|
||||
setSoundEnabled: jest.fn(),
|
||||
setSoundVolume: jest.fn(),
|
||||
setDiffEnabled: jest.fn(),
|
||||
setBrowserViewportSize: jest.fn(),
|
||||
setFuzzyMatchThreshold: jest.fn(),
|
||||
setPreferredLanguage: jest.fn(),
|
||||
setWriteDelayMs: jest.fn(),
|
||||
setScreenshotQuality: jest.fn(),
|
||||
setTerminalOutputLineLimit: jest.fn(),
|
||||
setMcpEnabled: jest.fn(),
|
||||
setAlwaysApproveResubmit: jest.fn(),
|
||||
setRequestDelaySeconds: jest.fn(),
|
||||
setCurrentApiConfigName: jest.fn(),
|
||||
setListApiConfigMeta: jest.fn(),
|
||||
onUpdateApiConfig: jest.fn(),
|
||||
setMode: jest.fn(),
|
||||
setCustomPrompts: jest.fn(),
|
||||
setEnhancementApiConfigId: jest.fn(),
|
||||
setAutoApprovalEnabled: jest.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseExtensionState.mockReturnValue(defaultMockState)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("renders with initial collapsed state", () => {
|
||||
render(<AutoApproveMenu />)
|
||||
|
||||
// Check for main checkbox and label
|
||||
expect(screen.getByText("Auto-approve:")).toBeInTheDocument()
|
||||
expect(screen.getByText("None")).toBeInTheDocument()
|
||||
|
||||
// Verify the menu is collapsed (actions not visible)
|
||||
expect(screen.queryByText("Read files and directories")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("expands menu when clicked", () => {
|
||||
render(<AutoApproveMenu />)
|
||||
|
||||
// Click to expand
|
||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
||||
|
||||
// Verify menu items are visible
|
||||
expect(screen.getByText("Read files and directories")).toBeInTheDocument()
|
||||
expect(screen.getByText("Edit files")).toBeInTheDocument()
|
||||
expect(screen.getByText("Execute safe commands")).toBeInTheDocument()
|
||||
expect(screen.getByText("Use the browser")).toBeInTheDocument()
|
||||
expect(screen.getByText("Use MCP servers")).toBeInTheDocument()
|
||||
expect(screen.getByText("Retry failed requests")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("toggles main auto-approval checkbox", () => {
|
||||
render(<AutoApproveMenu />)
|
||||
|
||||
const mainCheckbox = screen.getByRole("checkbox")
|
||||
fireEvent.click(mainCheckbox)
|
||||
|
||||
expect(defaultMockState.setAutoApprovalEnabled).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("toggles individual permissions", () => {
|
||||
render(<AutoApproveMenu />)
|
||||
|
||||
// Expand menu
|
||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
||||
|
||||
// Click read files checkbox
|
||||
fireEvent.click(screen.getByText("Read files and directories"))
|
||||
expect(defaultMockState.setAlwaysAllowReadOnly).toHaveBeenCalledWith(true)
|
||||
|
||||
// Click edit files checkbox
|
||||
fireEvent.click(screen.getByText("Edit files"))
|
||||
expect(defaultMockState.setAlwaysAllowWrite).toHaveBeenCalledWith(true)
|
||||
|
||||
// Click execute commands checkbox
|
||||
fireEvent.click(screen.getByText("Execute safe commands"))
|
||||
expect(defaultMockState.setAlwaysAllowExecute).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it("displays enabled actions in summary", () => {
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
...defaultMockState,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowWrite: true,
|
||||
autoApprovalEnabled: true,
|
||||
})
|
||||
|
||||
render(<AutoApproveMenu />)
|
||||
|
||||
// Check that enabled actions are shown in summary
|
||||
expect(screen.getByText("Read, Edit")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("preserves checkbox states", () => {
|
||||
// Mock state with some permissions enabled
|
||||
const mockState = {
|
||||
...defaultMockState,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowWrite: true,
|
||||
}
|
||||
|
||||
// Update mock to return our state
|
||||
mockUseExtensionState.mockReturnValue(mockState)
|
||||
|
||||
render(<AutoApproveMenu />)
|
||||
|
||||
// Expand menu
|
||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
||||
|
||||
// Verify read and edit checkboxes are checked
|
||||
expect(screen.getByLabelText("Read files and directories")).toBeInTheDocument()
|
||||
expect(screen.getByLabelText("Edit files")).toBeInTheDocument()
|
||||
|
||||
// Verify the setters haven't been called yet
|
||||
expect(mockState.setAlwaysAllowReadOnly).not.toHaveBeenCalled()
|
||||
expect(mockState.setAlwaysAllowWrite).not.toHaveBeenCalled()
|
||||
|
||||
// Collapse menu
|
||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
||||
|
||||
// Expand again
|
||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
||||
|
||||
// Verify checkboxes are still present
|
||||
expect(screen.getByLabelText("Read files and directories")).toBeInTheDocument()
|
||||
expect(screen.getByLabelText("Edit files")).toBeInTheDocument()
|
||||
|
||||
// Verify the setters still haven't been called
|
||||
expect(mockState.setAlwaysAllowReadOnly).not.toHaveBeenCalled()
|
||||
expect(mockState.setAlwaysAllowWrite).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,313 @@
|
||||
import React from 'react'
|
||||
import { render, waitFor } from '@testing-library/react'
|
||||
import ChatView from '../ChatView'
|
||||
import { ExtensionStateContextProvider } from '../../../context/ExtensionStateContext'
|
||||
import { vscode } from '../../../utils/vscode'
|
||||
|
||||
// Mock vscode API
|
||||
jest.mock('../../../utils/vscode', () => ({
|
||||
vscode: {
|
||||
postMessage: jest.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock all problematic dependencies
|
||||
jest.mock('rehype-highlight', () => ({
|
||||
__esModule: true,
|
||||
default: () => () => {},
|
||||
}))
|
||||
|
||||
jest.mock('hast-util-to-text', () => ({
|
||||
__esModule: true,
|
||||
default: () => '',
|
||||
}))
|
||||
|
||||
// Mock components that use ESM dependencies
|
||||
jest.mock('../BrowserSessionRow', () => ({
|
||||
__esModule: true,
|
||||
default: function MockBrowserSessionRow({ messages }: { messages: any[] }) {
|
||||
return <div data-testid="browser-session">{JSON.stringify(messages)}</div>
|
||||
}
|
||||
}))
|
||||
|
||||
jest.mock('../ChatRow', () => ({
|
||||
__esModule: true,
|
||||
default: function MockChatRow({ message }: { message: any }) {
|
||||
return <div data-testid="chat-row">{JSON.stringify(message)}</div>
|
||||
}
|
||||
}))
|
||||
|
||||
jest.mock('../TaskHeader', () => ({
|
||||
__esModule: true,
|
||||
default: function MockTaskHeader({ task }: { task: any }) {
|
||||
return <div data-testid="task-header">{JSON.stringify(task)}</div>
|
||||
}
|
||||
}))
|
||||
|
||||
jest.mock('../AutoApproveMenu', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
jest.mock('../../common/CodeBlock', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
CODE_BLOCK_BG_COLOR: 'rgb(30, 30, 30)',
|
||||
}))
|
||||
|
||||
jest.mock('../../common/CodeAccordian', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
jest.mock('../ContextMenu', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
// Mock window.postMessage to trigger state hydration
|
||||
const mockPostMessage = (state: any) => {
|
||||
window.postMessage({
|
||||
type: 'state',
|
||||
state: {
|
||||
version: '1.0.0',
|
||||
clineMessages: [],
|
||||
taskHistory: [],
|
||||
shouldShowAnnouncement: false,
|
||||
allowedCommands: [],
|
||||
alwaysAllowExecute: false,
|
||||
autoApprovalEnabled: true,
|
||||
...state
|
||||
}
|
||||
}, '*')
|
||||
}
|
||||
|
||||
describe('ChatView - Auto Approval Tests', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('auto-approves read operations when enabled', async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
alwaysAllowReadOnly: true,
|
||||
autoApprovalEnabled: true,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Then send the read tool ask message
|
||||
mockPostMessage({
|
||||
alwaysAllowReadOnly: 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: 'readFile', path: 'test.txt' }),
|
||||
partial: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Wait for the auto-approval message
|
||||
await waitFor(() => {
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: 'askResponse',
|
||||
askResponse: 'yesButtonClicked'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does not auto-approve when autoApprovalEnabled is false', async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
alwaysAllowReadOnly: true,
|
||||
autoApprovalEnabled: false,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Then send the read tool ask message
|
||||
mockPostMessage({
|
||||
alwaysAllowReadOnly: 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: 'readFile', path: 'test.txt' }),
|
||||
partial: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Verify no auto-approval message was sent
|
||||
expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
||||
type: 'askResponse',
|
||||
askResponse: 'yesButtonClicked'
|
||||
})
|
||||
})
|
||||
|
||||
it('auto-approves write operations when enabled', async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
alwaysAllowWrite: true,
|
||||
autoApprovalEnabled: true,
|
||||
writeDelayMs: 0,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Then send the write tool ask message
|
||||
mockPostMessage({
|
||||
alwaysAllowWrite: true,
|
||||
autoApprovalEnabled: true,
|
||||
writeDelayMs: 0,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
},
|
||||
{
|
||||
type: 'ask',
|
||||
ask: 'tool',
|
||||
ts: Date.now(),
|
||||
text: JSON.stringify({ tool: 'editedExistingFile', path: 'test.txt' }),
|
||||
partial: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Wait for the auto-approval message
|
||||
await waitFor(() => {
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: 'askResponse',
|
||||
askResponse: 'yesButtonClicked'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('auto-approves browser actions when enabled', async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
alwaysAllowBrowser: true,
|
||||
autoApprovalEnabled: true,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Then send the browser action ask message
|
||||
mockPostMessage({
|
||||
alwaysAllowBrowser: true,
|
||||
autoApprovalEnabled: true,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
},
|
||||
{
|
||||
type: 'ask',
|
||||
ask: 'browser_action_launch',
|
||||
ts: Date.now(),
|
||||
text: JSON.stringify({ action: 'launch', url: 'http://example.com' }),
|
||||
partial: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Wait for the auto-approval message
|
||||
await waitFor(() => {
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: 'askResponse',
|
||||
askResponse: 'yesButtonClicked'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -46,6 +46,11 @@ jest.mock('../ChatRow', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
jest.mock('../AutoApproveMenu', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
interface ChatTextAreaProps {
|
||||
onSend: (value: string) => void;
|
||||
inputValue?: string;
|
||||
@@ -139,6 +144,187 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('defaults autoApprovalEnabled to true if any individual auto-approval flags are true', async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
// Test cases with different individual flags
|
||||
const testCases = [
|
||||
{ alwaysAllowBrowser: true },
|
||||
{ alwaysAllowReadOnly: true },
|
||||
{ alwaysAllowWrite: true },
|
||||
{ alwaysAllowExecute: true },
|
||||
{ alwaysAllowMcp: true }
|
||||
]
|
||||
|
||||
for (const flags of testCases) {
|
||||
// Reset state
|
||||
mockPostMessage({
|
||||
...flags,
|
||||
clineMessages: []
|
||||
})
|
||||
|
||||
// Send an action that should be auto-approved
|
||||
mockPostMessage({
|
||||
...flags,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
},
|
||||
{
|
||||
type: 'ask',
|
||||
ask: flags.alwaysAllowBrowser ? 'browser_action_launch' :
|
||||
flags.alwaysAllowReadOnly ? 'tool' :
|
||||
flags.alwaysAllowWrite ? 'tool' :
|
||||
flags.alwaysAllowExecute ? 'command' :
|
||||
'use_mcp_server',
|
||||
ts: Date.now(),
|
||||
text: flags.alwaysAllowBrowser ? JSON.stringify({ action: 'launch', url: 'http://example.com' }) :
|
||||
flags.alwaysAllowReadOnly ? JSON.stringify({ tool: 'readFile', path: 'test.txt' }) :
|
||||
flags.alwaysAllowWrite ? JSON.stringify({ tool: 'editedExistingFile', path: 'test.txt' }) :
|
||||
flags.alwaysAllowExecute ? 'npm test' :
|
||||
JSON.stringify({ type: 'use_mcp_tool', serverName: 'test', toolName: 'test' }),
|
||||
partial: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Wait for auto-approval
|
||||
await waitFor(() => {
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: 'askResponse',
|
||||
askResponse: 'yesButtonClicked'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Verify no auto-approval when no flags are true
|
||||
jest.clearAllMocks()
|
||||
mockPostMessage({
|
||||
alwaysAllowBrowser: false,
|
||||
alwaysAllowReadOnly: false,
|
||||
alwaysAllowWrite: false,
|
||||
alwaysAllowExecute: false,
|
||||
alwaysAllowMcp: false,
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
},
|
||||
{
|
||||
type: 'ask',
|
||||
ask: 'browser_action_launch',
|
||||
ts: Date.now(),
|
||||
text: JSON.stringify({ action: 'launch', url: 'http://example.com' }),
|
||||
partial: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Wait a bit to ensure no auto-approval happens
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
||||
type: 'askResponse',
|
||||
askResponse: 'yesButtonClicked'
|
||||
})
|
||||
})
|
||||
|
||||
it('does not auto-approve any actions when autoApprovalEnabled is false', () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
<ChatView
|
||||
isHidden={false}
|
||||
showAnnouncement={false}
|
||||
hideAnnouncement={() => {}}
|
||||
showHistoryView={() => {}}
|
||||
/>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: false,
|
||||
alwaysAllowBrowser: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowWrite: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test'],
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Test various types of actions that should not be auto-approved
|
||||
const testCases = [
|
||||
{
|
||||
ask: 'browser_action_launch',
|
||||
text: JSON.stringify({ action: 'launch', url: 'http://example.com' })
|
||||
},
|
||||
{
|
||||
ask: 'tool',
|
||||
text: JSON.stringify({ tool: 'readFile', path: 'test.txt' })
|
||||
},
|
||||
{
|
||||
ask: 'tool',
|
||||
text: JSON.stringify({ tool: 'editedExistingFile', path: 'test.txt' })
|
||||
},
|
||||
{
|
||||
ask: 'command',
|
||||
text: 'npm test'
|
||||
}
|
||||
]
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: false,
|
||||
alwaysAllowBrowser: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
alwaysAllowWrite: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test'],
|
||||
clineMessages: [
|
||||
{
|
||||
type: 'say',
|
||||
say: 'task',
|
||||
ts: Date.now() - 2000,
|
||||
text: 'Initial task'
|
||||
},
|
||||
{
|
||||
type: 'ask',
|
||||
ask: testCase.ask,
|
||||
ts: Date.now(),
|
||||
text: testCase.text,
|
||||
partial: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Verify no auto-approval message was sent
|
||||
expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
||||
type: 'askResponse',
|
||||
askResponse: 'yesButtonClicked'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('auto-approves browser actions when alwaysAllowBrowser is enabled', async () => {
|
||||
render(
|
||||
<ExtensionStateContextProvider>
|
||||
@@ -153,6 +339,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowBrowser: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -166,6 +353,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send the browser action ask message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowBrowser: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -207,6 +395,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -220,6 +409,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send the read-only tool ask message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowReadOnly: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -262,6 +452,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
writeDelayMs: 0,
|
||||
clineMessages: [
|
||||
@@ -276,6 +467,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send the write tool ask message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
writeDelayMs: 0,
|
||||
clineMessages: [
|
||||
@@ -318,6 +510,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -331,6 +524,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send a non-tool write operation message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowWrite: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -371,6 +565,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test'],
|
||||
clineMessages: [
|
||||
@@ -385,6 +580,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send the command ask message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test'],
|
||||
clineMessages: [
|
||||
@@ -427,6 +623,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test'],
|
||||
clineMessages: [
|
||||
@@ -441,6 +638,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send the disallowed command ask message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test'],
|
||||
clineMessages: [
|
||||
@@ -498,6 +696,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test', 'npm run build', 'echo', 'Select-String'],
|
||||
clineMessages: [
|
||||
@@ -512,6 +711,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send the chained command ask message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test', 'npm run build', 'echo', 'Select-String'],
|
||||
clineMessages: [
|
||||
@@ -585,6 +785,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
|
||||
// Then send the chained command ask message
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test', 'Select-String'],
|
||||
clineMessages: [
|
||||
@@ -643,6 +844,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test', 'Select-String'],
|
||||
clineMessages: [
|
||||
@@ -656,6 +858,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
})
|
||||
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test', 'Select-String'],
|
||||
clineMessages: [
|
||||
@@ -688,6 +891,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test', 'Select-String'],
|
||||
clineMessages: [
|
||||
@@ -701,6 +905,7 @@ describe('ChatView - Auto Approval Tests', () => {
|
||||
})
|
||||
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowExecute: true,
|
||||
allowedCommands: ['npm test', 'Select-String'],
|
||||
clineMessages: [
|
||||
@@ -748,6 +953,7 @@ describe('ChatView - Sound Playing Tests', () => {
|
||||
|
||||
// First hydrate state with initial task and streaming
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowBrowser: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -768,6 +974,7 @@ describe('ChatView - Sound Playing Tests', () => {
|
||||
|
||||
// Then send the browser action ask message (streaming finished)
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowBrowser: true,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -807,6 +1014,7 @@ describe('ChatView - Sound Playing Tests', () => {
|
||||
|
||||
// First hydrate state with initial task and streaming
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowBrowser: false,
|
||||
clineMessages: [
|
||||
{
|
||||
@@ -827,6 +1035,7 @@ describe('ChatView - Sound Playing Tests', () => {
|
||||
|
||||
// Then send the browser action ask message (streaming finished)
|
||||
mockPostMessage({
|
||||
autoApprovalEnabled: true,
|
||||
alwaysAllowBrowser: false,
|
||||
clineMessages: [
|
||||
{
|
||||
|
||||
@@ -63,6 +63,8 @@ export interface ExtensionStateContextType extends ExtensionState {
|
||||
setCustomPrompts: (value: CustomPrompts) => void
|
||||
enhancementApiConfigId?: string
|
||||
setEnhancementApiConfigId: (value: string) => void
|
||||
autoApprovalEnabled?: boolean
|
||||
setAutoApprovalEnabled: (value: boolean) => void
|
||||
}
|
||||
|
||||
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
@@ -121,11 +123,22 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
const message: ExtensionMessage = event.data
|
||||
switch (message.type) {
|
||||
case "state": {
|
||||
const newState = message.state!
|
||||
// Set autoApprovalEnabled to true if undefined and any individual flag is true
|
||||
if (newState.autoApprovalEnabled === undefined) {
|
||||
newState.autoApprovalEnabled = !!(
|
||||
newState.alwaysAllowBrowser ||
|
||||
newState.alwaysAllowReadOnly ||
|
||||
newState.alwaysAllowWrite ||
|
||||
newState.alwaysAllowExecute ||
|
||||
newState.alwaysAllowMcp ||
|
||||
newState.alwaysApproveResubmit)
|
||||
}
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
...message.state!
|
||||
...newState
|
||||
}))
|
||||
const config = message.state?.apiConfiguration
|
||||
const config = newState.apiConfiguration
|
||||
const hasKey = checkExistKey(config)
|
||||
setShowWelcome(!hasKey)
|
||||
setDidHydrateState(true)
|
||||
@@ -237,6 +250,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
setMode: (value: Mode) => setState((prevState) => ({ ...prevState, mode: value })),
|
||||
setCustomPrompts: (value) => setState((prevState) => ({ ...prevState, customPrompts: value })),
|
||||
setEnhancementApiConfigId: (value) => setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
|
||||
setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user