diff --git a/gui/src/pages/config/components/ToolPolicyItem.tsx b/gui/src/pages/config/components/ToolPolicyItem.tsx index 5d81c5908f2..2971e015d4f 100644 --- a/gui/src/pages/config/components/ToolPolicyItem.tsx +++ b/gui/src/pages/config/components/ToolPolicyItem.tsx @@ -19,6 +19,7 @@ import { import { useFontSize } from "../../../components/ui/font"; import { useAppSelector } from "../../../redux/hooks"; import { addTool, setToolPolicy } from "../../../redux/slices/uiSlice"; +import { isEditTool } from "../../../util/toolCallState"; interface ToolPolicyItemProps { tool: Tool; @@ -28,12 +29,22 @@ interface ToolPolicyItemProps { export function ToolPolicyItem(props: ToolPolicyItemProps) { const dispatch = useDispatch(); - const policy = useAppSelector( + const toolPolicy = useAppSelector( (state) => state.ui.toolSettings[props.tool.function.name], ); const [isExpanded, setIsExpanded] = useState(false); const mode = useAppSelector((state) => state.session.mode); + const autoAcceptEditToolDiffs = useAppSelector( + (state) => state.config.config.ui?.autoAcceptEditToolDiffs, + ); + const isAutoAcceptedToolCall = + isEditTool(props.tool.function.name) && autoAcceptEditToolDiffs; + + const policy = isAutoAcceptedToolCall + ? "allowedWithoutPermission" + : toolPolicy; + useEffect(() => { if (!policy) { dispatch(addTool(props.tool)); @@ -53,6 +64,7 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) { const fontSize = useFontSize(-2); const disabled = + isAutoAcceptedToolCall || !props.isGroupEnabled || (mode === "plan" && props.tool.group === BUILT_IN_GROUP_NAME && @@ -100,6 +112,19 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) { ) : null} + {isAutoAcceptedToolCall ? ( + + Auto-Accept Agent Edits setting is on +

+ } + > + +
+ ) : null} {props.tool.faviconUrl && ( - {disabled || policy === "disabled" - ? "Excluded" - : policy === "allowedWithoutPermission" - ? "Automatic" - : "Ask First"} + {isAutoAcceptedToolCall + ? "Automatic" + : disabled || policy === "disabled" + ? "Excluded" + : policy === "allowedWithoutPermission" + ? "Automatic" + : "Ask First"} diff --git a/gui/src/redux/slices/sessionSlice.ts b/gui/src/redux/slices/sessionSlice.ts index 541787888a3..dee39f8565d 100644 --- a/gui/src/redux/slices/sessionSlice.ts +++ b/gui/src/redux/slices/sessionSlice.ts @@ -25,7 +25,6 @@ import { ToolCallState, } from "core"; import type { RemoteSessionMetadata } from "core/control-plane/client"; -import { BuiltInToolNames } from "core/tools/builtIn"; import { NEW_SESSION_TITLE } from "core/util/constants"; import { renderChatMessage, @@ -36,7 +35,7 @@ import { findLastIndex } from "lodash"; import { v4 as uuidv4 } from "uuid"; import { type InlineErrorMessageType } from "../../components/mainInput/InlineErrorMessage"; import { toolCallCtxItemToCtxItemWithId } from "../../pages/gui/ToolCallDiv/utils"; -import { addToolCallDeltaToState } from "../../util/toolCallState"; +import { addToolCallDeltaToState, isEditTool } from "../../util/toolCallState"; import { RootState } from "../store"; import { streamResponseThunk } from "../thunks/streamResponse"; import { findChatHistoryItemByToolCallId, findToolCallById } from "../util"; @@ -51,17 +50,10 @@ import { findChatHistoryItemByToolCallId, findToolCallById } from "../util"; function filterMultipleEditToolCalls( toolCalls: ToolCallDelta[], ): ToolCallDelta[] { - const editToolNames = [ - BuiltInToolNames.EditExistingFile, - BuiltInToolNames.SingleFindAndReplace, - BuiltInToolNames.MultiEdit, - ]; let hasSeenEditTool = false; return toolCalls.filter((toolCall) => { - const isEditTool = editToolNames.includes(toolCall.function?.name as any); - - if (isEditTool) { + if (toolCall.function?.name && isEditTool(toolCall.function?.name)) { if (hasSeenEditTool) { return false; // Skip this duplicate edit tool } diff --git a/gui/src/redux/thunks/evaluateToolPolicies.ts b/gui/src/redux/thunks/evaluateToolPolicies.ts index 990aef4c89e..37c009210a2 100644 --- a/gui/src/redux/thunks/evaluateToolPolicies.ts +++ b/gui/src/redux/thunks/evaluateToolPolicies.ts @@ -1,6 +1,7 @@ import { ToolPolicy } from "@continuedev/terminal-security"; import { Tool, ToolCallState } from "core"; import { IIdeMessenger } from "../../context/IdeMessenger"; +import { isEditTool } from "../../util/toolCallState"; import { errorToolCall, updateToolCallOutput } from "../slices/sessionSlice"; import { DEFAULT_TOOL_SETTING, ToolPolicies } from "../slices/uiSlice"; import { AppThunkDispatch } from "../store"; @@ -20,7 +21,16 @@ async function evaluateToolPolicy( activeTools: Tool[], toolCallState: ToolCallState, toolPolicies: ToolPolicies, + autoAcceptEditToolDiffs: boolean | undefined, ): Promise { + // allow edit tool calls without permission if auto-accept is enabled + if ( + isEditTool(toolCallState.toolCall.function.name) && + autoAcceptEditToolDiffs + ) { + return { policy: "allowedWithoutPermission", toolCallState }; + } + const basePolicy = toolPolicies[toolCallState.toolCall.function.name] ?? activeTools.find( @@ -73,6 +83,7 @@ export async function evaluateToolPolicies( activeTools: Tool[], generatedToolCalls: ToolCallState[], toolPolicies: ToolPolicies, + autoAcceptEditToolDiffs: boolean | undefined, ): Promise { // Check if ALL tool calls are auto-approved using dynamic evaluation const policyResults = await Promise.all( @@ -82,6 +93,7 @@ export async function evaluateToolPolicies( activeTools, toolCallState, toolPolicies, + autoAcceptEditToolDiffs, ), ), ); diff --git a/gui/src/redux/thunks/streamNormalInput.ts b/gui/src/redux/thunks/streamNormalInput.ts index b1f5d10ff2b..bf4939c38cd 100644 --- a/gui/src/redux/thunks/streamNormalInput.ts +++ b/gui/src/redux/thunks/streamNormalInput.ts @@ -268,6 +268,7 @@ export const streamNormalInput = createAsyncThunk< activeTools, generatedCalls3, toolPolicies, + state3.config.config.ui?.autoAcceptEditToolDiffs, ); const anyRequireApproval = policies.find( ({ policy }) => policy === "allowedWithPermission", diff --git a/gui/src/util/toolCallState.ts b/gui/src/util/toolCallState.ts index 92df3acd3eb..c7b9bcbd7df 100644 --- a/gui/src/util/toolCallState.ts +++ b/gui/src/util/toolCallState.ts @@ -1,4 +1,5 @@ import { ToolCallDelta, ToolCallState } from "core"; +import { BuiltInToolNames } from "core/tools/builtIn"; import { incrementalParseJson } from "core/util/incrementalParseJson"; // Merge streamed tool calls @@ -67,3 +68,12 @@ export function addToolCallDeltaToState( parsedArgs, }; } + +const editToolNames: string[] = [ + BuiltInToolNames.EditExistingFile, + BuiltInToolNames.SingleFindAndReplace, + BuiltInToolNames.MultiEdit, +]; +export function isEditTool(toolName: string) { + return editToolNames.includes(toolName); +}