diff --git a/packages/app/e2e/regression/remote-session-settings.spec.ts b/packages/app/e2e/regression/remote-session-settings.spec.ts index 35a0aa44cda8..83891780609d 100644 --- a/packages/app/e2e/regression/remote-session-settings.spec.ts +++ b/packages/app/e2e/regression/remote-session-settings.spec.ts @@ -11,11 +11,34 @@ const sessionA = session("ses_server_a", directoryA, "Server A session") const childSessionA = { ...session("ses_server_a_child", directoryA, "Server A child session"), parentID: sessionA.id } const sessionB = session("ses_server_b", directoryB, "Server B session") -test("session settings use the remote server context", async ({ page }) => { +test("auto-accept setting works without a session", async ({ page }) => { const permissionRequests: string[] = [] await mockServers(page, permissionRequests) await configureServers(page) + await page.goto("/") + await page.keyboard.press("Control+,") + + const input = page + .locator(".settings-v2-dialog") + .locator('[data-action="settings-auto-accept-permissions"]') + .getByRole("switch") + await expect(input).toBeEnabled() + await input.click() + await expect(input).toBeChecked() +}) + +test("session settings use the remote server context", async ({ page }) => { + const permissionRequests: string[] = [] + const permissionResponses: PermissionResponse[] = [] + const pendingB: MockPermission[] = [] + const permissionID = "permission-pending-b" + await mockServers(page, permissionRequests, permissionResponses, { + pending: { [serverB]: pendingB }, + replyFailures: { [permissionID]: 1 }, + }) + await configureServers(page) + await page.goto(`/server/${base64Encode(serverB)}/session/${sessionB.id}`) await expect(page.getByText(sessionB.title).first()).toBeVisible() await page.keyboard.press("Control+,") @@ -26,6 +49,7 @@ test("session settings use the remote server context", async ({ page }) => { await expect(autoAccept).toBeVisible() await expect(input).toBeEnabled() permissionRequests.length = 0 + pendingB.push(pendingPermission(permissionID, sessionB.id)) await autoAccept.locator('[data-slot="switch-control"]').click() await expect(input).toBeChecked() await expect @@ -36,7 +60,17 @@ test("session settings use the remote server context", async ({ page }) => { }), ) .toBe(true) - expect(permissionRequests.every((request) => new URL(request).origin === serverB)).toBe(true) + await expect + .poll(() => permissionResponses) + .toEqual([ + { + origin: serverB, + directory: directoryB, + sessionID: sessionB.id, + permissionID, + body: { response: "once" }, + }, + ]) await dialog.getByRole("tab", { name: "Models" }).click() await expect(dialog.getByRole("switch", { name: "Server B Model" })).toBeEnabled() @@ -150,6 +184,19 @@ type PermissionResponse = { body: unknown } +type MockPermission = { + id: string + sessionID: string + permission: string + patterns: string[] + metadata: Record + always: string[] +} + +function pendingPermission(id: string, sessionID: string): MockPermission { + return { id, sessionID, permission: "bash", patterns: ["git status"], metadata: {}, always: [] } +} + async function configureServers(page: Page, tabs: { type: "session"; server: string; sessionId: string }[] = []) { await page.addInitScript( ({ serverB, tabs }) => { @@ -161,7 +208,15 @@ async function configureServers(page: Page, tabs: { type: "session"; server: str ) } -async function mockServers(page: Page, permissionRequests: string[], permissionResponses: PermissionResponse[] = []) { +async function mockServers( + page: Page, + permissionRequests: string[], + permissionResponses: PermissionResponse[] = [], + options: { + pending?: Record + replyFailures?: Record + } = {}, +) { await page.route("**/*", async (route) => { const url = new URL(route.request().url()) if (url.origin !== serverA && url.origin !== serverB) return route.fallback() @@ -171,6 +226,11 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR const requestDirectory = url.searchParams.get("directory") const response = url.pathname.match(/^\/session\/([^/]+)\/permissions\/([^/]+)$/) if (route.request().method() === "POST" && response) { + const failures = options.replyFailures?.[response[2]!] ?? 0 + if (failures > 0) { + options.replyFailures![response[2]!] = failures - 1 + return json(route, { name: "Internal" }, 500) + } permissionResponses.push({ origin: url.origin, directory: requestDirectory ?? undefined, @@ -218,7 +278,7 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR if (/^\/session\/[^/]+\/(children|todo|diff)$/.test(url.pathname)) return json(route, []) if (url.pathname === "/permission") { permissionRequests.push(url.toString()) - return json(route, []) + return json(route, options.pending?.[url.origin] ?? []) } if (["/skill", "/command", "/lsp", "/formatter", "/question", "/vcs/diff", "/pty/shells"].includes(url.pathname)) return json(route, []) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 8adaa1f0fdf9..b844c894d79d 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -8,9 +8,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip" import { Tag } from "@opencode-ai/ui/v2/badge-v2" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context" import { useDialog } from "@opencode-ai/ui/context/dialog" -import { useParams } from "@solidjs/router" import { useLanguage } from "@/context/language" -import { usePermission } from "@/context/permission" import { usePlatform, type DisplayBackend } from "@/context/platform" import { useServerSync } from "@/context/server-sync" import { useServerSDK } from "@/context/server-sdk" @@ -27,7 +25,6 @@ import { terminalInput, useSettings, } from "@/context/settings" -import { decode64 } from "@/utils/base64" import { playSoundById, SOUND_OPTIONS } from "@/utils/sound" import { ExternalLink } from "./external-link" import { SettingsList } from "./settings-list" @@ -85,40 +82,13 @@ const playDemoSound = (id: string | undefined) => { export const SettingsGeneral: Component = () => { const theme = useTheme() const language = useLanguage() - const permission = usePermission() const platform = usePlatform() const dialog = useDialog() - const params = useParams() const settings = useSettings() const updater = useUpdaterAction() 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 desktop = createMemo(() => platform.platform === "desktop") const themeOptions = createMemo(() => theme.ids().map((id) => ({ id, name: theme.name(id) }))) @@ -321,7 +291,10 @@ export const SettingsGeneral: Component = () => { description={language.t("toast.permissions.autoaccept.on.description")} >
- + settings.permissions.setAutoApprove(checked)} + />
diff --git a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx index 4116f4a6208c..c2f7207e97e5 100644 --- a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx +++ b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx @@ -94,7 +94,7 @@ export const DialogSettings: Component<{ - + diff --git a/packages/app/src/components/settings-v2/general-controllers.ts b/packages/app/src/components/settings-v2/general-controllers.ts index ae77fa332f22..59919ebe9694 100644 --- a/packages/app/src/components/settings-v2/general-controllers.ts +++ b/packages/app/src/components/settings-v2/general-controllers.ts @@ -1,7 +1,6 @@ import { createMemo, createResource, onMount, type Accessor } from "solid-js" import type { ColorScheme } from "@opencode-ai/ui/theme/context" import { useTheme } from "@opencode-ai/ui/theme/context" -import { usePermission } from "@/context/permission" import { useServerSDK } from "@/context/server-sdk" import { useServerSync } from "@/context/server-sync" import { @@ -22,33 +21,6 @@ import { createSoundPreviewController, type ShellOption } from "./general-contro export { createShellOptions, createSoundPreviewController } from "./general-controller-behavior" export type { ShellOption, ShellSelectOption } from "./general-controller-behavior" -export function createPermissionScopeController(sessionID: Accessor) { - const permission = usePermission() - const serverSync = useServerSync() - const directory = createMemo(() => { - const id = sessionID() - if (!id) return undefined - return serverSync().session.lineage.peek(id)?.session.directory - }) - - return { - accepting: createMemo(() => { - const id = sessionID() - const dir = directory() - if (!id || !dir) return false - return permission.isAutoAccepting(id, dir) - }), - enabled: createMemo(() => !!directory()), - set: (checked: boolean) => { - const id = sessionID() - const dir = directory() - if (!id || !dir) return - if (checked) return permission.enableAutoAccept(id, dir) - permission.disableAutoAccept(id, dir) - }, - } -} - export function createShellSettingsController() { const serverSdk = useServerSDK() const serverSync = useServerSync() @@ -167,7 +139,6 @@ export function createSoundSettingsController() { } } -export type PermissionScopeController = ReturnType export type ShellSettingsController = ReturnType export type AppearanceSettingsController = ReturnType export type SoundSettingsController = ReturnType diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index b41da107ed6e..4dcf714992b2 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -15,13 +15,11 @@ import { SettingsRowV2 } from "./parts/row" import { LayoutRetirementNotice, LayoutTransitionToggle } from "./interface-transition" import { createAppearanceSettingsController, - createPermissionScopeController, createShellOptions, createShellSettingsController, createSoundSettingsController, soundOptions, type AppearanceSettingsController, - type PermissionScopeController, type ShellSettingsController, type SoundSettingsController, } from "./general-controllers" @@ -69,8 +67,9 @@ const soundSettings = { }, } as const -const PermissionScopeSetting: Component<{ controller: PermissionScopeController }> = (props) => { +const AutoApprovePermissionsSetting: Component = () => { const language = useLanguage() + const settings = useSettings() return (
settings.permissions.setAutoApprove(checked)} />
@@ -271,16 +269,13 @@ const LanguageSetting = () => { ) } -export const SettingsGeneralV2: Component<{ - sessionID?: string -}> = (props) => { +export const SettingsGeneralV2: Component = () => { const language = useLanguage() const platform = usePlatform() const dialog = useDialog() const settings = useSettings() const mobile = createMediaQuery("(max-width: 767px)") const updater = useUpdaterAction() - const permissionScope = createPermissionScopeController(() => props.sessionID) const shell = createShellSettingsController() const appearance = createAppearanceSettingsController() const sounds = createSoundSettingsController() @@ -329,7 +324,7 @@ export const SettingsGeneralV2: Component<{ - + diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx index d6d8019262a4..86619f4cfef7 100644 --- a/packages/app/src/context/permission.tsx +++ b/packages/app/src/context/permission.tsx @@ -1,8 +1,6 @@ -import { createEffect, createMemo, createRoot, getOwner, onCleanup } from "solid-js" -import { createStore, produce } from "solid-js/store" +import { createEffect, createMemo, createRoot, getOwner, onCleanup, type Accessor } from "solid-js" import { createSimpleContext } from "@opencode-ai/ui/context" import type { PermissionRequest } from "@opencode-ai/sdk/v2/client" -import { Persist, persisted } from "@/utils/persist" import type { ServerSDK } from "@/context/server-sdk" import type { ServerSync } from "./server-sync" import { useParams, useSearchParams } from "@solidjs/router" @@ -14,13 +12,6 @@ import { useSettings } from "./settings" import { requireServerKey } from "@/utils/session-route" import type { ServerScope } from "@/utils/server-scope" import { normalizePermissionRequest } from "./global-sync/utils" -import { - acceptKey, - directoryAcceptKey, - isDirectoryAutoAccepting, - autoRespondsPermission, - sessionAutoAccept, -} from "./permission-auto-respond" type PermissionRespondFn = (input: { sessionID: string @@ -91,7 +82,12 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple (dispose) => ({ key, dispose, - state: createServerPermissionState({ sdk: ctx.sdk, sync: ctx.sync }), + state: createServerPermissionState({ + sdk: ctx.sdk, + sync: ctx.sync, + autoApprove: settings.permissions.autoApprove, + setAutoApprove: settings.permissions.setAutoApprove, + }), }), owner ?? undefined, ) @@ -136,12 +132,6 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple return selected().sync.session.lineage.peek(params.id)?.session.directory }) - createEffect(() => { - const directory = activeDirectory() - if (!directory) return - selected().enableConfiguredDirectory(directory) - }) - const permissionsEnabled = createMemo(() => { const directory = activeDirectory() if (!directory) return false @@ -187,48 +177,17 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple type PermissionState = ReturnType type PermissionEvent = Parameters[0]>[0] -function createServerPermissionState(input: { sdk: ServerSDK; sync: ServerSync }) { - const [store, setStore, _, ready] = persisted( - { - ...Persist.serverGlobal(input.sdk.scope, "permission", ["permission.v3"]), - migrate(value) { - if (!value || typeof value !== "object" || Array.isArray(value)) return value - - const data = value as Record - if (data.autoAccept) return value - - return { - ...data, - autoAccept: - typeof data.autoAcceptEdits === "object" && data.autoAcceptEdits && !Array.isArray(data.autoAcceptEdits) - ? data.autoAcceptEdits - : {}, - } - }, - }, - createStore({ - autoAccept: {} as Record, - }), - ) - - function enableConfiguredDirectory(directory: string) { - if (input.sdk.protocolKind() !== "v1") return - if (meta.disposed || !ready()) return - const [childStore] = input.sync.child(directory) - if (childStore.config.permission !== "allow") return - const key = directoryAcceptKey(directory) - if (store.autoAccept[key] !== undefined) return - setStore( - produce((draft) => { - draft.autoAccept[key] = true - }), - ) - } - +function createServerPermissionState(input: { + sdk: ServerSDK + sync: ServerSync + autoApprove: Accessor + setAutoApprove: (value: boolean) => void +}) { const MAX_RESPONDED = 1000 const RESPONDED_TTL_MS = 60 * 60 * 1000 + const AUTO_RESPONSE_RETRY_LIMIT = 2 + const AUTO_RESPONSE_RETRY_DELAY_MS = 1000 const responded = new Map() - const enableVersion = new Map() const meta = { disposed: false } function pruneResponded(now: number) { @@ -266,19 +225,26 @@ function createServerPermissionState(input: { sdk: ServerSDK; sync: ServerSync } .then((result) => result.data.map(normalizePermissionRequest)) } - function respondOnce(permission: PermissionRequest, directory?: string) { + function respondOnce(permission: PermissionRequest, directory?: string, attempt = 0) { + if (meta.disposed || !input.autoApprove()) return const now = Date.now() const hit = responded.has(permission.id) responded.delete(permission.id) responded.set(permission.id, now) pruneResponded(now) if (hit) return - respond({ - sessionID: permission.sessionID, - permissionID: permission.id, - response: "once", - directory, - }) + input.sdk.api.permission + .reply({ + sessionID: permission.sessionID, + requestID: permission.id, + reply: "once", + location: directory ? { directory } : undefined, + }) + .catch(() => { + responded.delete(permission.id) + if (meta.disposed || !input.autoApprove() || attempt >= AUTO_RESPONSE_RETRY_LIMIT) return + setTimeout(() => respondOnce(permission, directory, attempt + 1), AUTO_RESPONSE_RETRY_DELAY_MS * (attempt + 1)) + }) } function sessions(directory?: string) { @@ -288,142 +254,94 @@ function createServerPermissionState(input: { sdk: ServerSDK; sync: ServerSync } } function isAutoAccepting(sessionID: string, directory?: string) { - return autoRespondsPermission(store.autoAccept, sessions(directory), { sessionID }, directory) + void sessionID + void directory + return input.autoApprove() } function isAutoAcceptingDirectory(directory: string) { - return isDirectoryAutoAccepting(store.autoAccept, directory) + void directory + return input.autoApprove() } function shouldAutoRespond(permission: PermissionRequest, directory?: string) { - return autoRespondsPermission(store.autoAccept, sessions(directory), permission, directory) - } - - function isPending(permission: PermissionRequest) { - const pending = input.sync.session.data.permission[permission.sessionID] - return pending === undefined || pending.some((item) => item.id === permission.id) - } - - async function shouldAutoRespondResolved(permission: PermissionRequest, directory?: string) { - const override = sessionAutoAccept(store.autoAccept, sessions(directory), permission, directory) - if (override !== undefined) return override - if (input.sync.session.lineage.peek(permission.sessionID)) return shouldAutoRespond(permission, directory) - const lineage = await input.sync.session.lineage.resolve(permission.sessionID).catch(() => undefined) - if (meta.disposed || !lineage) return false - return shouldAutoRespond(permission, directory) + void permission + void directory + return input.autoApprove() } - async function respondPending( - permission: PermissionRequest, - directory?: string, - current: () => boolean = () => true, - ) { - if (!current() || !isPending(permission)) return - if (!(await shouldAutoRespondResolved(permission, directory))) return - if (meta.disposed || !current() || !isPending(permission)) return - respondOnce(permission, directory) - } - - function bumpEnableVersion(sessionID: string, directory?: string) { - const key = acceptKey(sessionID, directory) - const next = (enableVersion.get(key) ?? 0) + 1 - enableVersion.set(key, next) - return next + const SWEEP_RETRY_LIMIT = 2 + let sweepGeneration = 0 + + function sweepPending(attempt = 0, generation = ++sweepGeneration) { + if (meta.disposed || !input.autoApprove()) return + void input.sdk.api.session + .active() + .then((active) => + Promise.all( + Object.keys(active).map((sessionID) => + input.sync.session.resolve(sessionID, { force: true }).then( + () => true, + () => false, + ), + ), + ), + ) + .then((resolved) => + Promise.all( + [ + ...new Set( + sessions() + .map((session) => session.directory) + .filter((directory): directory is string => !!directory), + ), + ].map((directory) => + list(directory).then( + (permissions) => { + permissions.forEach((permission) => respondOnce(permission, directory)) + return true + }, + () => false, + ), + ), + ).then((listed) => resolved.every(Boolean) && listed.every(Boolean)), + ) + .catch(() => false) + .then((complete) => { + if (complete || meta.disposed || !input.autoApprove() || generation !== sweepGeneration) return + if (attempt >= SWEEP_RETRY_LIMIT) return + setTimeout(() => sweepPending(attempt + 1, generation), 1000 * (attempt + 1)) + }) } const handlePermission = (e: PermissionEvent) => { const event = e.details + if (event?.type === "server.connected") { + sweepPending() + return + } if (event?.type !== "permission.asked") return - void respondPending(event.properties, e.name) + respondOnce(event.properties, e.name) } const unsubscribe = input.sdk.event.listen((event) => { - if (ready()) { - handlePermission(event) - return - } - void ready.promise?.then(() => { - if (meta.disposed) return - handlePermission(event) - }) + handlePermission(event) }) onCleanup(() => { meta.disposed = true unsubscribe() }) - function enableDirectory(directory: string) { - if (meta.disposed) return - const key = directoryAcceptKey(directory) - setStore( - produce((draft) => { - draft.autoAccept[key] = true - }), - ) - - list(directory) - .then((permissions) => { - if (meta.disposed) return - if (!isAutoAcceptingDirectory(directory)) return - for (const permission of permissions) { - void respondPending(permission, directory, () => isAutoAcceptingDirectory(directory)) - } - }) - .catch(() => undefined) - } - - function disableDirectory(directory: string) { - if (meta.disposed) return - const key = directoryAcceptKey(directory) - setStore( - produce((draft) => { - draft.autoAccept[key] = false - }), - ) - } - - function enable(sessionID: string, directory: string) { - if (meta.disposed) return - const key = acceptKey(sessionID, directory) - const version = bumpEnableVersion(sessionID, directory) - setStore( - produce((draft) => { - draft.autoAccept[key] = true - delete draft.autoAccept[sessionID] - }), - ) - - list(directory) - .then((permissions) => { - if (meta.disposed) return - if (enableVersion.get(key) !== version) return - if (!isAutoAccepting(sessionID, directory)) return - for (const permission of permissions) { - void respondPending( - permission, - directory, - () => enableVersion.get(key) === version && isAutoAccepting(sessionID, directory), - ) - } - }) - .catch(() => undefined) - } - - function disable(sessionID: string, directory?: string) { - if (meta.disposed) return - bumpEnableVersion(sessionID, directory) - const key = directory ? acceptKey(sessionID, directory) : sessionID - setStore( - produce((draft) => { - draft.autoAccept[key] = false - if (!directory) return - delete draft.autoAccept[sessionID] - }), - ) - } + createEffect(() => { + if (!input.autoApprove()) { + sweepGeneration++ + return + } + sweepPending() + }) const api = { - ready: () => !meta.disposed && ready(), + ready: () => !meta.disposed, respond, autoResponds(permission: PermissionRequest, directory?: string) { if (meta.disposed) return false @@ -438,30 +356,29 @@ function createServerPermissionState(input: { sdk: ServerSDK; sync: ServerSync } return isAutoAcceptingDirectory(directory) }, toggleAutoAccept(sessionID: string, directory: string) { + void sessionID + void directory if (meta.disposed) return - if (isAutoAccepting(sessionID, directory)) { - disable(sessionID, directory) - return - } - - enable(sessionID, directory) + input.setAutoApprove(!input.autoApprove()) }, toggleAutoAcceptDirectory(directory: string) { + void directory if (meta.disposed) return - if (isAutoAcceptingDirectory(directory)) { - disableDirectory(directory) - return - } - enableDirectory(directory) + input.setAutoApprove(!input.autoApprove()) }, enableAutoAccept(sessionID: string, directory: string) { + void sessionID + void directory if (meta.disposed) return - if (isAutoAccepting(sessionID, directory)) return - enable(sessionID, directory) + if (input.autoApprove()) return + input.setAutoApprove(true) }, disableAutoAccept(sessionID: string, directory?: string) { + void sessionID + void directory if (meta.disposed) return - disable(sessionID, directory) + if (!input.autoApprove()) return + input.setAutoApprove(false) }, isPermissionAllowAll(directory: string) { if (meta.disposed) return false @@ -474,7 +391,6 @@ function createServerPermissionState(input: { sdk: ServerSDK; sync: ServerSync } ...api, api, sync: input.sync, - enableConfiguredDirectory, permissionsEnabled(directory: string) { if (meta.disposed) return false const [childStore] = input.sync.child(directory) diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 18d5b172ff17..6991c5487830 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -5,7 +5,6 @@ import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-b import { useFile, selectionFromLines, type FileSelection, type SelectedLineRange } from "@/context/file" import { useLanguage } from "@/context/language" import { useLayout } from "@/context/layout" -import { usePermission } from "@/context/permission" import { usePrompt } from "@/context/prompt" import { useSDK } from "@/context/sdk" import { useSettings } from "@/context/settings" @@ -42,7 +41,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const dialog = useDialog() const file = useFile() const language = useLanguage() - const permission = usePermission() const prompt = usePrompt() const sdk = useSDK() const settings = useSettings() @@ -142,11 +140,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const mcpCommand = withCategory(language.t("command.category.mcp")) const permissionsCommand = withCategory(language.t("command.category.permissions")) - const isAutoAcceptActive = () => { - const sessionID = params.id - if (sessionID) return permission.isAutoAccepting(sessionID, sdk().directory) - return permission.isAutoAcceptingDirectory(sdk().directory) - } const write = async (value: string) => { const body = typeof document === "undefined" ? undefined : document.body if (body) { @@ -314,13 +307,8 @@ export const useSessionCommands = (actions: SessionCommandContext) => { } const toggleAutoAccept = () => { - const sessionID = params.id - if (sessionID) permission.toggleAutoAccept(sessionID, sdk().directory) - else permission.toggleAutoAcceptDirectory(sdk().directory) - - const active = sessionID - ? permission.isAutoAccepting(sessionID, sdk().directory) - : permission.isAutoAcceptingDirectory(sdk().directory) + const active = !settings.permissions.autoApprove() + settings.permissions.setAutoApprove(active) showToast({ title: active ? language.t("toast.permissions.autoaccept.on.title") @@ -634,7 +622,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const permissionsCmds = () => [ permissionsCommand({ id: "permissions.autoaccept", - title: isAutoAcceptActive() + title: settings.permissions.autoApprove() ? language.t("command.permissions.autoaccept.disable") : language.t("command.permissions.autoaccept.enable"), keybind: "mod+shift+a",