mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Add AutoApprovalSettings state
This commit is contained in:
parent
cd54c501b4
commit
f406147a30
7 changed files with 200 additions and 83 deletions
|
|
@ -46,9 +46,9 @@ import { formatResponse } from "./prompts/responses"
|
|||
import { addCustomInstructions, SYSTEM_PROMPT } from "./prompts/system"
|
||||
import { truncateHalfConversation } from "./sliding-window"
|
||||
import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider"
|
||||
import { showOmissionWarning } from "../integrations/editor/detect-omission"
|
||||
import { BrowserSession } from "../services/browser/BrowserSession"
|
||||
import { constructNewFileContent } from "./assistant-message/diff"
|
||||
import { AutoApprovalSettings } from "../shared/AutoApprovalSettings"
|
||||
|
||||
const cwd =
|
||||
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
|
||||
|
|
@ -67,6 +67,7 @@ export class Cline {
|
|||
private didEditFile: boolean = false
|
||||
customInstructions?: string
|
||||
alwaysAllowReadOnly: boolean
|
||||
autoApprovalSettings: AutoApprovalSettings
|
||||
apiConversationHistory: Anthropic.MessageParam[] = []
|
||||
clineMessages: ClineMessage[] = []
|
||||
private askResponse?: ClineAskResponse
|
||||
|
|
@ -94,6 +95,7 @@ export class Cline {
|
|||
constructor(
|
||||
provider: ClineProvider,
|
||||
apiConfiguration: ApiConfiguration,
|
||||
autoApprovalSettings: AutoApprovalSettings,
|
||||
customInstructions?: string,
|
||||
alwaysAllowReadOnly?: boolean,
|
||||
task?: string,
|
||||
|
|
@ -108,7 +110,7 @@ export class Cline {
|
|||
this.diffViewProvider = new DiffViewProvider(cwd)
|
||||
this.customInstructions = customInstructions
|
||||
this.alwaysAllowReadOnly = alwaysAllowReadOnly ?? false
|
||||
|
||||
this.autoApprovalSettings = autoApprovalSettings
|
||||
if (historyItem) {
|
||||
this.taskId = historyItem.id
|
||||
this.resumeTaskFromHistory()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { Cline } from "../Cline"
|
|||
import { openMention } from "../mentions"
|
||||
import { getNonce } from "./getNonce"
|
||||
import { getUri } from "./getUri"
|
||||
import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
|
|
@ -59,6 +60,7 @@ type GlobalStateKey =
|
|||
| "azureApiVersion"
|
||||
| "openRouterModelId"
|
||||
| "openRouterModelInfo"
|
||||
| "autoApprovalSettings"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
|
|
@ -198,16 +200,27 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
|
||||
async initClineWithTask(task?: string, images?: string[]) {
|
||||
await this.clearTask() // ensures that an exising task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
|
||||
const { apiConfiguration, customInstructions, alwaysAllowReadOnly } = await this.getState()
|
||||
this.cline = new Cline(this, apiConfiguration, customInstructions, alwaysAllowReadOnly, task, images)
|
||||
const { apiConfiguration, customInstructions, alwaysAllowReadOnly, autoApprovalSettings } =
|
||||
await this.getState()
|
||||
this.cline = new Cline(
|
||||
this,
|
||||
apiConfiguration,
|
||||
autoApprovalSettings,
|
||||
customInstructions,
|
||||
alwaysAllowReadOnly,
|
||||
task,
|
||||
images,
|
||||
)
|
||||
}
|
||||
|
||||
async initClineWithHistoryItem(historyItem: HistoryItem) {
|
||||
await this.clearTask()
|
||||
const { apiConfiguration, customInstructions, alwaysAllowReadOnly } = await this.getState()
|
||||
const { apiConfiguration, customInstructions, alwaysAllowReadOnly, autoApprovalSettings } =
|
||||
await this.getState()
|
||||
this.cline = new Cline(
|
||||
this,
|
||||
apiConfiguration,
|
||||
autoApprovalSettings,
|
||||
customInstructions,
|
||||
alwaysAllowReadOnly,
|
||||
undefined,
|
||||
|
|
@ -420,6 +433,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
}
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "autoApprovalSettings":
|
||||
if (message.autoApprovalSettings) {
|
||||
await this.updateGlobalState("autoApprovalSettings", message.autoApprovalSettings)
|
||||
if (this.cline) {
|
||||
this.cline.autoApprovalSettings = message.autoApprovalSettings
|
||||
}
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
break
|
||||
case "askResponse":
|
||||
this.cline?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
|
||||
break
|
||||
|
|
@ -824,8 +846,14 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
}
|
||||
|
||||
async getStateToPostToWebview() {
|
||||
const { apiConfiguration, lastShownAnnouncementId, customInstructions, alwaysAllowReadOnly, taskHistory } =
|
||||
await this.getState()
|
||||
const {
|
||||
apiConfiguration,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
alwaysAllowReadOnly,
|
||||
taskHistory,
|
||||
autoApprovalSettings,
|
||||
} = await this.getState()
|
||||
return {
|
||||
version: this.context.extension?.packageJSON?.version ?? "",
|
||||
apiConfiguration,
|
||||
|
|
@ -835,6 +863,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
clineMessages: this.cline?.clineMessages || [],
|
||||
taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts),
|
||||
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
|
||||
autoApprovalSettings,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -919,6 +948,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
customInstructions,
|
||||
alwaysAllowReadOnly,
|
||||
taskHistory,
|
||||
autoApprovalSettings,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
|
|
@ -948,6 +978,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getGlobalState("customInstructions") as Promise<string | undefined>,
|
||||
this.getGlobalState("alwaysAllowReadOnly") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
|
||||
this.getGlobalState("autoApprovalSettings") as Promise<AutoApprovalSettings | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
|
|
@ -995,6 +1026,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
customInstructions,
|
||||
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
|
||||
taskHistory,
|
||||
autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
28
src/shared/AutoApprovalSettings.ts
Normal file
28
src/shared/AutoApprovalSettings.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
export interface AutoApprovalSettings {
|
||||
// Whether auto-approval is enabled
|
||||
enabled: boolean
|
||||
// Individual action permissions
|
||||
actions: {
|
||||
readFiles: boolean // Read files and directories
|
||||
editFiles: boolean // Edit files
|
||||
executeCommands: boolean // Execute safe commands
|
||||
useBrowser: boolean // Use browser
|
||||
useMcp: boolean // Use MCP servers
|
||||
}
|
||||
// Global settings
|
||||
maxRequests: number // Maximum number of auto-approved requests
|
||||
enableNotifications: boolean // Show notifications for approval and task completion
|
||||
}
|
||||
|
||||
export const DEFAULT_AUTO_APPROVAL_SETTINGS: AutoApprovalSettings = {
|
||||
enabled: false,
|
||||
actions: {
|
||||
readFiles: false,
|
||||
editFiles: false,
|
||||
executeCommands: false,
|
||||
useBrowser: false,
|
||||
useMcp: false,
|
||||
},
|
||||
maxRequests: 20,
|
||||
enableNotifications: false,
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// type that represents json data that is sent from extension to webview, called ExtensionMessage and has 'type' enum which can be 'plusButtonClicked' or 'settingsButtonClicked' or 'hello'
|
||||
|
||||
import { ApiConfiguration, ModelInfo } from "./api"
|
||||
import { AutoApprovalSettings } from "./AutoApprovalSettings"
|
||||
import { HistoryItem } from "./HistoryItem"
|
||||
import { McpServer } from "./mcp"
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ export interface ExtensionState {
|
|||
clineMessages: ClineMessage[]
|
||||
taskHistory: HistoryItem[]
|
||||
shouldShowAnnouncement: boolean
|
||||
autoApprovalSettings: AutoApprovalSettings
|
||||
}
|
||||
|
||||
export interface ClineMessage {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ApiConfiguration, ApiProvider } from "./api"
|
||||
import { ApiConfiguration } from "./api"
|
||||
import { AutoApprovalSettings } from "./AutoApprovalSettings"
|
||||
|
||||
export interface WebviewMessage {
|
||||
type:
|
||||
|
|
@ -25,11 +26,13 @@ export interface WebviewMessage {
|
|||
| "refreshOpenRouterModels"
|
||||
| "openMcpSettings"
|
||||
| "restartMcpServer"
|
||||
| "autoApprovalSettings"
|
||||
text?: string
|
||||
askResponse?: ClineAskResponse
|
||||
apiConfiguration?: ApiConfiguration
|
||||
images?: string[]
|
||||
bool?: boolean
|
||||
autoApprovalSettings?: AutoApprovalSettings
|
||||
}
|
||||
|
||||
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
|
||||
|
|
|
|||
|
|
@ -1,70 +1,116 @@
|
|||
import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useCallback, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
|
||||
interface AutoApproveAction {
|
||||
id: string
|
||||
label: string
|
||||
enabled: boolean
|
||||
description: string
|
||||
}
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { AutoApprovalSettings } from "../../../../src/shared/AutoApprovalSettings"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
|
||||
interface AutoApproveMenuProps {
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_REQUESTS = 50
|
||||
const ACTION_METADATA: {
|
||||
id: keyof AutoApprovalSettings["actions"]
|
||||
label: string
|
||||
shortName: string
|
||||
description: string
|
||||
}[] = [
|
||||
{
|
||||
id: "readFiles",
|
||||
label: "Read files and directories",
|
||||
shortName: "Read",
|
||||
description: "Allows access to read any file on your computer.",
|
||||
},
|
||||
{
|
||||
id: "editFiles",
|
||||
label: "Edit files",
|
||||
shortName: "Edit",
|
||||
description: "Allows modification of any files on your computer.",
|
||||
},
|
||||
{
|
||||
id: "executeCommands",
|
||||
label: "Execute safe commands",
|
||||
shortName: "Commands",
|
||||
description:
|
||||
"Allows execution of safe terminal commands. If the model determines a command is potentially destructive, it will still require approval.",
|
||||
},
|
||||
{
|
||||
id: "useBrowser",
|
||||
label: "Use the browser",
|
||||
shortName: "Browser",
|
||||
description: "Allows ability to launch and interact with any website in a headless browser.",
|
||||
},
|
||||
{
|
||||
id: "useMcp",
|
||||
label: "Use MCP servers",
|
||||
shortName: "MCP",
|
||||
description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.",
|
||||
},
|
||||
]
|
||||
|
||||
const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
const { autoApprovalSettings } = useExtensionState()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [actions, setActions] = useState<AutoApproveAction[]>([
|
||||
{
|
||||
id: "readFiles",
|
||||
label: "Read files and directories",
|
||||
enabled: false,
|
||||
description: "Allows access to read any file on your computer.",
|
||||
},
|
||||
{
|
||||
id: "editFiles",
|
||||
label: "Edit files",
|
||||
enabled: false,
|
||||
description: "Allows modification of any files on your computer.",
|
||||
},
|
||||
{
|
||||
id: "executeCommands",
|
||||
label: "Execute safe commands",
|
||||
enabled: false,
|
||||
description:
|
||||
"Allows automatic execution of safe terminal commands. The model will determine if a command is potentially destructive and ask for explicit approval.",
|
||||
},
|
||||
{
|
||||
id: "useBrowser",
|
||||
label: "Use the browser",
|
||||
enabled: false,
|
||||
description: "Allows ability to launch and interact with any website in a headless browser.",
|
||||
},
|
||||
{
|
||||
id: "useMcp",
|
||||
label: "Use MCP servers",
|
||||
enabled: 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)
|
||||
|
||||
const toggleExpanded = useCallback(() => {
|
||||
setIsExpanded((prev) => !prev)
|
||||
}, [])
|
||||
// Careful not to use partials to mutate since spread operator only does shallow copy
|
||||
|
||||
const toggleAction = useCallback((actionId: string) => {
|
||||
setActions((prev) =>
|
||||
prev.map((action) => (action.id === actionId ? { ...action, enabled: !action.enabled } : action)),
|
||||
)
|
||||
}, [])
|
||||
const updateEnabled = useCallback(
|
||||
(enabled: boolean) => {
|
||||
vscode.postMessage({
|
||||
type: "autoApprovalSettings",
|
||||
autoApprovalSettings: {
|
||||
...autoApprovalSettings,
|
||||
enabled,
|
||||
},
|
||||
})
|
||||
},
|
||||
[autoApprovalSettings],
|
||||
)
|
||||
|
||||
const enabledActions = actions.filter((action) => action.enabled)
|
||||
const enabledActionsList = enabledActions.map((action) => action.label).join(", ")
|
||||
const updateAction = useCallback(
|
||||
(actionId: keyof AutoApprovalSettings["actions"], value: boolean) => {
|
||||
vscode.postMessage({
|
||||
type: "autoApprovalSettings",
|
||||
autoApprovalSettings: {
|
||||
...autoApprovalSettings,
|
||||
actions: {
|
||||
...autoApprovalSettings.actions,
|
||||
[actionId]: value,
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
[autoApprovalSettings],
|
||||
)
|
||||
|
||||
const updateMaxRequests = useCallback(
|
||||
(maxRequests: number) => {
|
||||
vscode.postMessage({
|
||||
type: "autoApprovalSettings",
|
||||
autoApprovalSettings: {
|
||||
...autoApprovalSettings,
|
||||
maxRequests,
|
||||
},
|
||||
})
|
||||
},
|
||||
[autoApprovalSettings],
|
||||
)
|
||||
|
||||
const updateNotifications = useCallback(
|
||||
(enableNotifications: boolean) => {
|
||||
vscode.postMessage({
|
||||
type: "autoApprovalSettings",
|
||||
autoApprovalSettings: {
|
||||
...autoApprovalSettings,
|
||||
enableNotifications,
|
||||
},
|
||||
})
|
||||
},
|
||||
[autoApprovalSettings],
|
||||
)
|
||||
|
||||
const enabledActions = ACTION_METADATA.filter((action) => autoApprovalSettings.actions[action.id])
|
||||
const enabledActionsList = enabledActions.map((action) => action.shortName).join(", ")
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -85,23 +131,17 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
|||
padding: isExpanded ? "8px 0" : "8px 0 0 0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={toggleExpanded}>
|
||||
onClick={() => setIsExpanded((prev) => !prev)}>
|
||||
<VSCodeCheckbox
|
||||
checked={enabledActions.length > 0}
|
||||
checked={autoApprovalSettings.enabled}
|
||||
onChange={(e) => {
|
||||
const checked = (e.target as HTMLInputElement).checked
|
||||
setActions((prev) =>
|
||||
prev.map((action) => ({
|
||||
...action,
|
||||
enabled: checked,
|
||||
})),
|
||||
)
|
||||
e.stopPropagation()
|
||||
updateEnabled(checked)
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<CollapsibleSection>
|
||||
<span style={{ color: "var(--vscode-foreground)" }}>Auto-approve:</span>
|
||||
<span style={{ color: "var(--vscode-foreground)", whiteSpace: "nowrap" }}>Auto-approve:</span>
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
|
|
@ -113,7 +153,6 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
|||
<span
|
||||
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
|
||||
style={{
|
||||
// fontSize: "14px",
|
||||
flexShrink: 0,
|
||||
marginLeft: isExpanded ? "2px" : "-2px",
|
||||
}}
|
||||
|
|
@ -128,12 +167,18 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
|||
color: "var(--vscode-descriptionForeground)",
|
||||
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.
|
||||
Auto-approve allows Cline to perform the following actions without asking for permission. This
|
||||
is potentially dangerous and could lead to unwanted system modifications. Please use with
|
||||
caution and only enable if you understand the risks.
|
||||
</div>
|
||||
{actions.map((action) => (
|
||||
{ACTION_METADATA.map((action) => (
|
||||
<div key={action.id} style={{ margin: "6px 0" }}>
|
||||
<VSCodeCheckbox checked={action.enabled} onChange={() => toggleAction(action.id)}>
|
||||
<VSCodeCheckbox
|
||||
checked={autoApprovalSettings.actions[action.id]}
|
||||
onChange={(e) => {
|
||||
const checked = (e.target as HTMLInputElement).checked
|
||||
updateAction(action.id, checked)
|
||||
}}>
|
||||
{action.label}
|
||||
</VSCodeCheckbox>
|
||||
<div
|
||||
|
|
@ -165,11 +210,11 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
|||
}}>
|
||||
<span style={{ flexShrink: 1, minWidth: 0 }}>Max Requests:</span>
|
||||
<VSCodeTextField
|
||||
value={maxRequests.toString()}
|
||||
onChange={(e) => {
|
||||
value={autoApprovalSettings.maxRequests.toString()}
|
||||
onInput={(e) => {
|
||||
const value = parseInt((e.target as HTMLInputElement).value)
|
||||
if (!isNaN(value) && value > 0) {
|
||||
setMaxRequests(value)
|
||||
updateMaxRequests(value)
|
||||
}
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
|
|
@ -185,8 +230,11 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
|||
</div>
|
||||
<div style={{ margin: "6px 0" }}>
|
||||
<VSCodeCheckbox
|
||||
checked={enableNotifications}
|
||||
onChange={() => setEnableNotifications((prev) => !prev)}>
|
||||
checked={autoApprovalSettings.enableNotifications}
|
||||
onChange={(e) => {
|
||||
const checked = (e.target as HTMLInputElement).checked
|
||||
updateNotifications(checked)
|
||||
}}>
|
||||
Enable Notifications
|
||||
</VSCodeCheckbox>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { createContext, useCallback, useContext, useEffect, useState } from "react"
|
||||
import { useEvent } from "react-use"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../../src/shared/AutoApprovalSettings"
|
||||
import { ExtensionMessage, ExtensionState } from "../../../src/shared/ExtensionMessage"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
|
|
@ -7,10 +8,10 @@ import {
|
|||
openRouterDefaultModelId,
|
||||
openRouterDefaultModelInfo,
|
||||
} from "../../../src/shared/api"
|
||||
import { vscode } from "../utils/vscode"
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
import { findLastIndex } from "../../../src/shared/array"
|
||||
import { McpServer } from "../../../src/shared/mcp"
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
import { vscode } from "../utils/vscode"
|
||||
|
||||
interface ExtensionStateContextType extends ExtensionState {
|
||||
didHydrateState: boolean
|
||||
|
|
@ -33,6 +34,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
clineMessages: [],
|
||||
taskHistory: [],
|
||||
shouldShowAnnouncement: false,
|
||||
autoApprovalSettings: DEFAULT_AUTO_APPROVAL_SETTINGS,
|
||||
})
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue