Move auto-accept permissions to settings (#21308)

pull/12822/head^2
Shoubhit Dash 2026-04-07 16:30:13 +05:30 committed by GitHub
parent c2d2ca3522
commit 41612b3dbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 55 deletions

View File

@ -1,6 +1,6 @@
import type { ToolPart } from "@opencode-ai/sdk/v2/client" import type { ToolPart } from "@opencode-ai/sdk/v2/client"
import { test, expect } from "../fixtures" import { test, expect } from "../fixtures"
import { withSession } from "../actions" import { closeDialog, openSettings, withSession } from "../actions"
import { promptModelSelector, promptSelector, promptVariantSelector } from "../selectors" import { promptModelSelector, promptSelector, promptVariantSelector } from "../selectors"
const isBash = (part: unknown): part is ToolPart => { const isBash = (part: unknown): part is ToolPart => {
@ -19,12 +19,15 @@ test("shell mode runs a command in the project directory", async ({ page, projec
await withSession(project.sdk, `e2e shell ${Date.now()}`, async (session) => { await withSession(project.sdk, `e2e shell ${Date.now()}`, async (session) => {
project.trackSession(session.id) project.trackSession(session.id)
await project.gotoSession(session.id) await project.gotoSession(session.id)
const button = page.locator('[data-action="prompt-permissions"]').first() const dialog = await openSettings(page)
await expect(button).toBeVisible() const toggle = dialog.locator('[data-action="settings-auto-accept-permissions"]').first()
if ((await button.getAttribute("aria-pressed")) !== "true") { const input = toggle.locator('[data-slot="switch-input"]').first()
await button.click() await expect(toggle).toBeVisible()
await expect(button).toHaveAttribute("aria-pressed", "true") if ((await input.getAttribute("aria-checked")) !== "true") {
await toggle.locator('[data-slot="switch-control"]').click()
await expect(input).toHaveAttribute("aria-checked", "true")
} }
await closeDialog(page, dialog)
await project.shell(cmd) await project.shell(cmd)
await expect await expect

View File

@ -5,7 +5,7 @@ import {
type ComposerProbeState, type ComposerProbeState,
type ComposerWindow, type ComposerWindow,
} from "../../src/testing/session-composer" } from "../../src/testing/session-composer"
import { cleanupSession, clearSessionDockSeed, seedSessionQuestion } from "../actions" import { cleanupSession, clearSessionDockSeed, closeDialog, openSettings, seedSessionQuestion } from "../actions"
import { import {
permissionDockSelector, permissionDockSelector,
promptSelector, promptSelector,
@ -65,12 +65,14 @@ async function clearPermissionDock(page: any, label: RegExp) {
} }
async function setAutoAccept(page: any, enabled: boolean) { async function setAutoAccept(page: any, enabled: boolean) {
const button = page.locator('[data-action="prompt-permissions"]').first() const dialog = await openSettings(page)
await expect(button).toBeVisible() const toggle = dialog.locator('[data-action="settings-auto-accept-permissions"]').first()
const pressed = (await button.getAttribute("aria-pressed")) === "true" const input = toggle.locator('[data-slot="switch-input"]').first()
if (pressed === enabled) return await expect(toggle).toBeVisible()
await button.click() const checked = (await input.getAttribute("aria-checked")) === "true"
await expect(button).toHaveAttribute("aria-pressed", enabled ? "true" : "false") if (checked !== enabled) await toggle.locator('[data-slot="switch-control"]').click()
await expect(input).toHaveAttribute("aria-checked", enabled ? "true" : "false")
await closeDialog(page, dialog)
} }
async function expectQuestionBlocked(page: any) { async function expectQuestionBlocked(page: any) {
@ -277,6 +279,7 @@ test("default dock shows prompt input", async ({ page, project }) => {
await expect(page.locator(sessionComposerDockSelector)).toBeVisible() await expect(page.locator(sessionComposerDockSelector)).toBeVisible()
await expect(page.locator(promptSelector)).toBeVisible() await expect(page.locator(promptSelector)).toBeVisible()
await expect(page.locator('[data-action="prompt-permissions"]')).toHaveCount(0)
await expect(page.locator(questionDockSelector)).toHaveCount(0) await expect(page.locator(questionDockSelector)).toHaveCount(0)
await expect(page.locator(permissionDockSelector)).toHaveCount(0) await expect(page.locator(permissionDockSelector)).toHaveCount(0)
@ -290,10 +293,6 @@ test("default dock shows prompt input", async ({ page, project }) => {
test("auto-accept toggle works before first submit", async ({ page, project }) => { test("auto-accept toggle works before first submit", async ({ page, project }) => {
await project.open() await project.open()
const button = page.locator('[data-action="prompt-permissions"]').first()
await expect(button).toBeVisible()
await expect(button).toHaveAttribute("aria-pressed", "false")
await setAutoAccept(page, true) await setAutoAccept(page, true)
await setAutoAccept(page, false) await setAutoAccept(page, false)
}) })

View File

@ -1079,17 +1079,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory) if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
return permission.isAutoAccepting(id, sdk.directory) return permission.isAutoAccepting(id, sdk.directory)
}) })
const acceptLabel = createMemo(() =>
language.t(accepting() ? "command.permissions.autoaccept.disable" : "command.permissions.autoaccept.enable"),
)
const toggleAccept = () => {
if (!params.id) {
permission.toggleAutoAcceptDirectory(sdk.directory)
return
}
permission.toggleAutoAccept(params.id, sdk.directory)
}
const { abort, handleSubmit } = createPromptSubmit({ const { abort, handleSubmit } = createPromptSubmit({
info, info,
@ -1333,11 +1322,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onMouseDown={(e) => { onMouseDown={(e) => {
const target = e.target const target = e.target
if (!(target instanceof HTMLElement)) return if (!(target instanceof HTMLElement)) return
if ( if (target.closest('[data-action="prompt-attach"], [data-action="prompt-submit"]')) {
target.closest(
'[data-action="prompt-attach"], [data-action="prompt-submit"], [data-action="prompt-permissions"]',
)
) {
return return
} }
editorRef?.focus() editorRef?.focus()
@ -1597,28 +1582,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</TooltipKeybind> </TooltipKeybind>
</div> </div>
</Show> </Show>
<TooltipKeybind
placement="top"
gutter={8}
title={acceptLabel()}
keybind={command.keybind("permissions.autoaccept")}
>
<Button
data-action="prompt-permissions"
variant="ghost"
onClick={toggleAccept}
classList={{
"h-7 w-7 p-0 shrink-0 flex items-center justify-center": true,
"text-text-base": !accepting(),
"hover:bg-surface-success-base": accepting(),
}}
style={control()}
aria-label={acceptLabel()}
aria-pressed={accepting()}
>
<Icon name="shield" size="small" classList={{ "text-icon-success-base": accepting() }} />
</Button>
</TooltipKeybind>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,7 +8,9 @@ import { TextField } from "@opencode-ai/ui/text-field"
import { Tooltip } from "@opencode-ai/ui/tooltip" import { Tooltip } from "@opencode-ai/ui/tooltip"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
import { showToast } from "@opencode-ai/ui/toast" import { showToast } from "@opencode-ai/ui/toast"
import { useParams } from "@solidjs/router"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { usePermission } from "@/context/permission"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { import {
monoDefault, monoDefault,
@ -19,6 +21,7 @@ import {
sansInput, sansInput,
useSettings, useSettings,
} from "@/context/settings" } from "@/context/settings"
import { decode64 } from "@/utils/base64"
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound" import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "./link" import { Link } from "./link"
import { SettingsList } from "./settings-list" import { SettingsList } from "./settings-list"
@ -64,7 +67,9 @@ const playDemoSound = (id: string | undefined) => {
export const SettingsGeneral: Component = () => { export const SettingsGeneral: Component = () => {
const theme = useTheme() const theme = useTheme()
const language = useLanguage() const language = useLanguage()
const permission = usePermission()
const platform = usePlatform() const platform = usePlatform()
const params = useParams()
const settings = useSettings() const settings = useSettings()
onMount(() => { onMount(() => {
@ -76,6 +81,31 @@ export const SettingsGeneral: Component = () => {
}) })
const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux") const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
const dir = createMemo(() => decode64(params.dir))
const accepting = createMemo(() => {
const value = dir()
if (!value) return false
if (!params.id) return permission.isAutoAcceptingDirectory(value)
return permission.isAutoAccepting(params.id, value)
})
const toggleAccept = (checked: boolean) => {
const value = dir()
if (!value) return
if (!params.id) {
if (permission.isAutoAcceptingDirectory(value) === checked) return
permission.toggleAutoAcceptDirectory(value)
return
}
if (checked) {
permission.enableAutoAccept(params.id, value)
return
}
permission.disableAutoAccept(params.id, value)
}
const check = () => { const check = () => {
if (!platform.checkUpdate) return if (!platform.checkUpdate) return
@ -201,6 +231,15 @@ export const SettingsGeneral: Component = () => {
/> />
</SettingsRow> </SettingsRow>
<SettingsRow
title={language.t("command.permissions.autoaccept.enable")}
description={language.t("toast.permissions.autoaccept.on.description")}
>
<div data-action="settings-auto-accept-permissions">
<Switch checked={accepting()} disabled={!dir()} onChange={toggleAccept} />
</div>
</SettingsRow>
<SettingsRow <SettingsRow
title={language.t("settings.general.row.reasoningSummaries.title")} title={language.t("settings.general.row.reasoningSummaries.title")}
description={language.t("settings.general.row.reasoningSummaries.description")} description={language.t("settings.general.row.reasoningSummaries.description")}