diff --git a/core/config/migrateSharedConfig.ts b/core/config/migrateSharedConfig.ts index e27a631dc6e..8b18b41428d 100644 --- a/core/config/migrateSharedConfig.ts +++ b/core/config/migrateSharedConfig.ts @@ -173,13 +173,6 @@ export function migrateJsonSharedConfig(filepath: string, ide: IDE): void { effected = true; } - const { autoAcceptEditToolDiffs, ...withoutAutoApply } = migratedUI; - if (autoAcceptEditToolDiffs !== undefined) { - shareConfigUpdates.autoAcceptEditToolDiffs = autoAcceptEditToolDiffs; - migratedUI = withoutAutoApply; - effected = true; - } - const { showChatScrollbar, ...withoutShowChatScrollbar } = migratedUI; if (showChatScrollbar !== undefined) { shareConfigUpdates.showChatScrollbar = showChatScrollbar; diff --git a/core/config/sharedConfig.ts b/core/config/sharedConfig.ts index 52b96061aa2..87306a2aed8 100644 --- a/core/config/sharedConfig.ts +++ b/core/config/sharedConfig.ts @@ -31,7 +31,6 @@ export const sharedConfigSchema = z codeWrap: z.boolean(), displayRawMarkdown: z.boolean(), showChatScrollbar: z.boolean(), - autoAcceptEditToolDiffs: z.boolean(), continueAfterToolRejection: z.boolean(), // `tabAutocompleteOptions` in `ContinueConfig` @@ -140,10 +139,6 @@ export function modifyAnyConfigWithSharedConfig< if (sharedConfig.showChatScrollbar !== undefined) { configCopy.ui.showChatScrollbar = sharedConfig.showChatScrollbar; } - if (sharedConfig.autoAcceptEditToolDiffs !== undefined) { - configCopy.ui.autoAcceptEditToolDiffs = - sharedConfig.autoAcceptEditToolDiffs; - } if (sharedConfig.allowAnonymousTelemetry !== undefined) { configCopy.allowAnonymousTelemetry = sharedConfig.allowAnonymousTelemetry; diff --git a/core/index.d.ts b/core/index.d.ts index 87bc1ba1395..83d896e34f9 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -1389,7 +1389,6 @@ export interface ContinueUIConfig { showChatScrollbar?: boolean; codeWrap?: boolean; showSessionTabs?: boolean; - autoAcceptEditToolDiffs?: boolean; continueAfterToolRejection?: boolean; } diff --git a/gui/src/pages/config/components/ToolPoliciesGroup.tsx b/gui/src/pages/config/components/ToolPoliciesGroup.tsx index 796b715f63b..3561f9e7316 100644 --- a/gui/src/pages/config/components/ToolPoliciesGroup.tsx +++ b/gui/src/pages/config/components/ToolPoliciesGroup.tsx @@ -2,6 +2,7 @@ import { ChevronDownIcon, WrenchScrewdriverIcon, } from "@heroicons/react/24/outline"; +import { Tool } from "core"; import { useMemo, useState } from "react"; import ToggleSwitch from "../../../components/gui/Switch"; import { ToolTip } from "../../../components/gui/Tooltip"; @@ -28,7 +29,9 @@ export function ToolPoliciesGroup({ const dispatch = useAppDispatch(); const [isExpanded, setIsExpanded] = useState(false); - const availableTools = useAppSelector((state) => state.config.config.tools); + const availableTools = useAppSelector( + (state) => state.config.config.tools as Tool[], + ); const tools = useMemo(() => { return availableTools.filter((t) => t.group === groupName); }, [availableTools, groupName]); diff --git a/gui/src/pages/config/components/ToolPolicyItem.tsx b/gui/src/pages/config/components/ToolPolicyItem.tsx index 2971e015d4f..5d81c5908f2 100644 --- a/gui/src/pages/config/components/ToolPolicyItem.tsx +++ b/gui/src/pages/config/components/ToolPolicyItem.tsx @@ -19,7 +19,6 @@ 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; @@ -29,22 +28,12 @@ interface ToolPolicyItemProps { export function ToolPolicyItem(props: ToolPolicyItemProps) { const dispatch = useDispatch(); - const toolPolicy = useAppSelector( + const policy = 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)); @@ -64,7 +53,6 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) { const fontSize = useFontSize(-2); const disabled = - isAutoAcceptedToolCall || !props.isGroupEnabled || (mode === "plan" && props.tool.group === BUILT_IN_GROUP_NAME && @@ -112,19 +100,6 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) { ) : null} - {isAutoAcceptedToolCall ? ( - - Auto-Accept Agent Edits setting is on -

- } - > - -
- ) : null} {props.tool.faviconUrl && ( - {isAutoAcceptedToolCall - ? "Automatic" - : disabled || policy === "disabled" - ? "Excluded" - : policy === "allowedWithoutPermission" - ? "Automatic" - : "Ask First"} + {disabled || policy === "disabled" + ? "Excluded" + : policy === "allowedWithoutPermission" + ? "Automatic" + : "Ask First"} diff --git a/gui/src/pages/config/sections/UserSettingsSection.tsx b/gui/src/pages/config/sections/UserSettingsSection.tsx index afa56aca4ef..27b7f9bb183 100644 --- a/gui/src/pages/config/sections/UserSettingsSection.tsx +++ b/gui/src/pages/config/sections/UserSettingsSection.tsx @@ -64,7 +64,6 @@ export function UserSettingsSection() { const codeWrap = config.ui?.codeWrap ?? false; const showChatScrollbar = config.ui?.showChatScrollbar ?? false; const readResponseTTS = config.experimental?.readResponseTTS ?? false; - const autoAcceptEditToolDiffs = config.ui?.autoAcceptEditToolDiffs ?? false; const displayRawMarkdown = config.ui?.displayRawMarkdown ?? false; const disableSessionTitles = config.disableSessionTitles ?? false; const useCurrentFileAsContext = @@ -163,15 +162,6 @@ export function UserSettingsSection() { handleUpdate({ displayRawMarkdown: !value }) } /> - - handleUpdate({ autoAcceptEditToolDiffs: value }) - } - /> diff --git a/gui/src/pages/gui/chat-tests/EditToolScenarios.test.tsx b/gui/src/pages/gui/chat-tests/EditToolScenarios.test.tsx index dd95e09a979..b06264f8daa 100644 --- a/gui/src/pages/gui/chat-tests/EditToolScenarios.test.tsx +++ b/gui/src/pages/gui/chat-tests/EditToolScenarios.test.tsx @@ -1,9 +1,4 @@ import { BuiltInToolNames } from "core/tools/builtIn"; -import { generateToolCallButtonTestId } from "../../../components/mainInput/Lump/LumpToolbar/PendingToolCallToolbar"; -import { - addAndSelectMockLlm, - triggerConfigUpdate, -} from "../../../util/test/config"; import { updateConfig } from "../../../redux/slices/configSlice"; import { renderWithProviders } from "../../../util/test/render"; import { Chat } from "../Chat"; @@ -11,7 +6,6 @@ import { Chat } from "../Chat"; import { waitFor } from "@testing-library/dom"; import { act } from "@testing-library/react"; import { ChatMessage } from "core"; -import { setToolPolicy } from "../../../redux/slices/uiSlice"; import { setInactive } from "../../../redux/slices/sessionSlice"; import { getElementByTestId, @@ -133,12 +127,6 @@ test( await user.click(toggleCodeblockChevron); await getElementByText(EDIT_CHANGES); - // Pending tool call - find and click the accept button - const acceptToolCallButton = await getElementByTestId( - generateToolCallButtonTestId("accept", EDIT_TOOL_CALL_ID), - ); - await user.click(acceptToolCallButton); - // Tool call, check that applyToFile was called for edit await waitFor(() => { expect(messengerRequestSpy).toHaveBeenCalledWith("applyToFile", { diff --git a/gui/src/pages/gui/chat-tests/EditToolScenariosYolo.test.tsx b/gui/src/pages/gui/chat-tests/EditToolScenariosYolo.test.tsx deleted file mode 100644 index c13d46773ee..00000000000 --- a/gui/src/pages/gui/chat-tests/EditToolScenariosYolo.test.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import { BuiltInToolNames } from "core/tools/builtIn"; -import { generateToolCallButtonTestId } from "../../../components/mainInput/Lump/LumpToolbar/PendingToolCallToolbar"; -import { triggerConfigUpdate } from "../../../util/test/config"; -import { updateConfig } from "../../../redux/slices/configSlice"; -import { renderWithProviders } from "../../../util/test/render"; -import { Chat } from "../Chat"; - -import { waitFor } from "@testing-library/dom"; -import { act } from "@testing-library/react"; -import { ChatMessage } from "core"; -import { setToolPolicy } from "../../../redux/slices/uiSlice"; -import { setInactive } from "../../../redux/slices/sessionSlice"; -import { - getElementByTestId, - getElementByText, - sendInputWithMockedResponse, - verifyNotPresentByTestId, -} from "../../../util/test/utils"; - -const EDIT_WORKSPACE_DIR = "file:///workspace"; -const EDIT_FILEPATH = "test.txt"; -const EDIT_FILE_URI = `${EDIT_WORKSPACE_DIR}/${EDIT_FILEPATH}`; -const EDIT_CHANGES = "New content"; -const EDIT_TOOL_CALL_ID = "known-id"; -const EDIT_MESSAGES: ChatMessage[] = [ - { - role: "assistant", - content: "I'll edit the file for you.", - }, - { - role: "assistant", - content: "", - toolCalls: [ - { - id: EDIT_TOOL_CALL_ID, - function: { - name: BuiltInToolNames.EditExistingFile, - arguments: JSON.stringify({ - filepath: EDIT_FILEPATH, - changes: EDIT_CHANGES, - }), - }, - }, - ], - }, -]; - -const POST_EDIT_RESPONSE = "Edit applied successfully"; -beforeEach(async () => { - vi.clearAllMocks(); - // Clear any persisted state to ensure test isolation - localStorage.clear(); - sessionStorage.clear(); - - // Add a small delay to ensure cleanup is complete - await new Promise((resolve) => setTimeout(resolve, 50)); -}); - -test("Edit run with no policy and yolo mode", { timeout: 15000 }, async () => { - // Additional cleanup before test starts - localStorage.clear(); - sessionStorage.clear(); - await new Promise((resolve) => setTimeout(resolve, 100)); - - // Setup - const { ideMessenger, store, user } = await renderWithProviders(); - - // Reset mocks to ensure clean state - ideMessenger.resetMocks(); - - // Reset streaming state to prevent test interference - store.dispatch(setInactive()); - - // Additional delay to ensure state is fully reset - await new Promise((resolve) => setTimeout(resolve, 100)); - - ideMessenger.responses["getWorkspaceDirs"] = [EDIT_WORKSPACE_DIR]; - ideMessenger.responses["tools/evaluatePolicy"] = { - policy: "allowedWithoutPermission", - }; - - // Mock context/getSymbolsForFiles to prevent errors during streaming - ideMessenger.responses["context/getSymbolsForFiles"] = {}; - - const messengerPostSpy = vi.spyOn(ideMessenger, "post"); - const messengerRequestSpy = vi.spyOn(ideMessenger, "request"); - - // Instead of using addAndSelectMockLlm (which relies on events that might be failing), - // directly dispatch the config update to set the selected model - const currentConfig = store.getState().config.config; - store.dispatch( - updateConfig({ - ...currentConfig, - selectedModelByRole: { - ...currentConfig.selectedModelByRole, - chat: { - model: "mock", - provider: "mock", - title: "Mock LLM", - underlyingProviderName: "mock", - }, - }, - modelsByRole: { - ...currentConfig.modelsByRole, - chat: [ - ...(currentConfig.modelsByRole.chat || []), - { - model: "mock", - provider: "mock", - title: "Mock LLM", - underlyingProviderName: "mock", - }, - ], - }, - }), - ); - - // Enable automatic edit and yolo mode - store.dispatch( - setToolPolicy({ - toolName: BuiltInToolNames.EditExistingFile, - policy: "allowedWithoutPermission", - }), - ); - triggerConfigUpdate({ - store, - ideMessenger, - editConfig: (current) => { - current.ui = { - ...current.ui, - autoAcceptEditToolDiffs: true, // Enable auto-accept for edit tool diffs - }; - return current; - }, - }); - expect(store.getState().config.config.ui?.autoAcceptEditToolDiffs).toBe(true); - - // Send the input that will respond with an edit tool call - await sendInputWithMockedResponse( - ideMessenger, - "Edit this file", - EDIT_MESSAGES, - ); - - // Wait for streaming to complete and tool calls to be processed - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }); - - // Toggle the codeblock and make sure the changes show - const toggleCodeblockChevron = await getElementByTestId("toggle-codeblock"); - - await user.click(toggleCodeblockChevron); - await getElementByText(EDIT_CHANGES); - - // Make sure there's no pending tool call - await verifyNotPresentByTestId( - generateToolCallButtonTestId("accept", EDIT_TOOL_CALL_ID), - ); - // Tool call, check that applyToFile was called for edit - await waitFor(() => { - expect(messengerRequestSpy).toHaveBeenCalledWith("applyToFile", { - streamId: expect.any(String), - filepath: EDIT_FILE_URI, - text: EDIT_CHANGES, - toolCallId: EDIT_TOOL_CALL_ID, - }); - }); - - // Extract stream ID and initiate mock streaming - const streamId = messengerRequestSpy.mock.calls.find( - (call) => call[0] === "applyToFile", - )?.[1]?.streamId; - expect(streamId).toBeDefined(); - - ideMessenger.mockMessageToWebview("updateApplyState", { - status: "streaming", - streamId, - toolCallId: EDIT_TOOL_CALL_ID, - filepath: EDIT_FILE_URI, - }); - - // Mid stream, should show applying in notch - await getElementByTestId("notch-applying-text"); - await getElementByTestId("notch-applying-cancel-button"); - - // Close the stream - ideMessenger.mockMessageToWebview("updateApplyState", { - status: "done", - streamId, - toolCallId: EDIT_TOOL_CALL_ID, - filepath: EDIT_FILE_URI, - }); - - // Set the chat response text before the auto-accept triggers - ideMessenger.setChatResponseText(POST_EDIT_RESPONSE); - - await waitFor(() => { - expect(messengerPostSpy).toHaveBeenCalledWith("acceptDiff", { - streamId, - filepath: EDIT_FILE_URI, - }); - }); - - // Close the stream, ensure response is shown and diff buttons are not present - ideMessenger.mockMessageToWebview("updateApplyState", { - status: "closed", - streamId, - toolCallId: EDIT_TOOL_CALL_ID, - filepath: EDIT_FILE_URI, - }); - - // Allow time for the auto-streaming to complete - await new Promise((resolve) => setTimeout(resolve, 1000)); - - await verifyNotPresentByTestId("edit-accept-button"); - await verifyNotPresentByTestId("edit-reject-button"); - - // Try to manually trigger the streaming by checking if the response is set - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 500)); - }); - - // TODO: Fix this test - the POST_EDIT_RESPONSE is not being displayed - // await waitFor(() => getElementByText(POST_EDIT_RESPONSE), { - // timeout: 8000, - // }); -}); diff --git a/gui/src/redux/thunks/evaluateToolPolicies.ts b/gui/src/redux/thunks/evaluateToolPolicies.ts index 37c009210a2..d25cad6797e 100644 --- a/gui/src/redux/thunks/evaluateToolPolicies.ts +++ b/gui/src/redux/thunks/evaluateToolPolicies.ts @@ -21,13 +21,9 @@ 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 - ) { + // allow edit tool calls without permission + if (isEditTool(toolCallState.toolCall.function.name)) { return { policy: "allowedWithoutPermission", toolCallState }; } @@ -83,7 +79,6 @@ 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( @@ -93,7 +88,6 @@ export async function evaluateToolPolicies( activeTools, toolCallState, toolPolicies, - autoAcceptEditToolDiffs, ), ), ); diff --git a/gui/src/redux/thunks/handleApplyStateUpdate.test.ts b/gui/src/redux/thunks/handleApplyStateUpdate.test.ts index 46ae54c35fc..2e426b7d7c1 100644 --- a/gui/src/redux/thunks/handleApplyStateUpdate.test.ts +++ b/gui/src/redux/thunks/handleApplyStateUpdate.test.ts @@ -161,61 +161,6 @@ describe("handleApplyStateUpdate", () => { expect.objectContaining({ type: "session/updateApplyState" }), ); }); - - it("should auto-accept diffs when configured and status is done", async () => { - mockGetState.mockReturnValue({ - config: { - config: { - ui: { - autoAcceptEditToolDiffs: true, - }, - }, - }, - session: { history: [] }, - }); - - const applyState: ApplyState = { - streamId: "chat-stream", - toolCallId: "test-tool-call", - status: "done", - filepath: "test.txt", - numDiffs: 1, - }; - - const thunk = handleApplyStateUpdate(applyState); - await thunk(mockDispatch, mockGetState, mockExtra); - - expect(mockExtra.ideMessenger.post).toHaveBeenCalledWith("acceptDiff", { - streamId: "chat-stream", - filepath: "test.txt", - }); - }); - - it("should not auto-accept when config is disabled", async () => { - mockGetState.mockReturnValue({ - config: { - config: { - ui: { - autoAcceptEditToolDiffs: false, - }, - }, - }, - session: { history: [] }, - }); - - const applyState: ApplyState = { - streamId: "chat-stream", - toolCallId: "test-tool-call", - status: "done", - filepath: "test.txt", - numDiffs: 1, - }; - - const thunk = handleApplyStateUpdate(applyState); - await thunk(mockDispatch, mockGetState, mockExtra); - - expect(mockExtra.ideMessenger.post).not.toHaveBeenCalled(); - }); }); describe("closed status handling", () => { diff --git a/gui/src/redux/thunks/handleApplyStateUpdate.ts b/gui/src/redux/thunks/handleApplyStateUpdate.ts index 1cc2c35d07d..bbb0b520697 100644 --- a/gui/src/redux/thunks/handleApplyStateUpdate.ts +++ b/gui/src/redux/thunks/handleApplyStateUpdate.ts @@ -44,15 +44,6 @@ export const handleApplyStateUpdate = createAsyncThunk< // Handle apply status updates - use toolCallId from event payload if (applyState.toolCallId) { - if ( - applyState.status === "done" && - getState().config.config?.ui?.autoAcceptEditToolDiffs - ) { - extra.ideMessenger.post("acceptDiff", { - streamId: applyState.streamId, - filepath: applyState.filepath, - }); - } if (applyState.status === "closed") { // Find the tool call to check if it was canceled const toolCallState = findToolCallById( diff --git a/gui/src/redux/thunks/streamNormalInput.ts b/gui/src/redux/thunks/streamNormalInput.ts index 226146e072a..96cd2bb16f3 100644 --- a/gui/src/redux/thunks/streamNormalInput.ts +++ b/gui/src/redux/thunks/streamNormalInput.ts @@ -317,7 +317,6 @@ export const streamNormalInput = createAsyncThunk< activeTools, generatedCalls3, toolPolicies, - state3.config.config.ui?.autoAcceptEditToolDiffs, ); const anyRequireApproval = policies.find( ({ policy }) => policy === "allowedWithPermission",