summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorLuis Felipe Cordeiro Sena <[email protected]>2026-03-06 07:49:12 -0300
committerGitHub <[email protected]>2026-03-06 04:49:12 -0600
commitb7605add5803becb0a1abd9fb5110cb636cc6d01 (patch)
tree6acb89080fc3acee0715495990646862579108a4 /packages
parent6c7d968c4423a0cd6c85099c9377a6066313fa0a (diff)
downloadopencode-b7605add5803becb0a1abd9fb5110cb636cc6d01.tar.gz
opencode-b7605add5803becb0a1abd9fb5110cb636cc6d01.zip
fix(app): enable auto-accept keybind regardless of permission config (#16259)
Diffstat (limited to 'packages')
-rw-r--r--packages/app/src/components/prompt-input.tsx12
-rw-r--r--packages/app/src/context/permission-auto-respond.test.ts41
-rw-r--r--packages/app/src/context/permission-auto-respond.ts12
-rw-r--r--packages/app/src/context/permission.tsx70
-rw-r--r--packages/app/src/pages/session/use-session-commands.tsx29
-rwxr-xr-xpackages/opencode/script/build.ts6
6 files changed, 145 insertions, 25 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index c9c8bc6b4..40104bceb 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -244,7 +244,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
draggingType: "image" | "@mention" | null
mode: "normal" | "shell"
applyingHistory: boolean
- pendingAutoAccept: boolean
}>({
popover: null,
historyIndex: -1,
@@ -253,7 +252,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
draggingType: null,
mode: "normal",
applyingHistory: false,
- pendingAutoAccept: false,
})
const buttonsSpring = useSpring(() => (store.mode === "normal" ? 1 : 0), { visualDuration: 0.2, bounce: 0 })
@@ -306,12 +304,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}),
)
- createEffect(
- on(sessionKey, () => {
- setStore("pendingAutoAccept", false)
- }),
- )
-
const historyComments = () => {
const byID = new Map(comments.all().map((item) => [`${item.file}\n${item.id}`, item] as const))
return prompt.context.items().flatMap((item) => {
@@ -961,7 +953,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const variants = createMemo(() => ["default", ...local.model.variant.list()])
const accepting = createMemo(() => {
const id = params.id
- if (!id) return store.pendingAutoAccept
+ if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
return permission.isAutoAccepting(id, sdk.directory)
})
@@ -1336,7 +1328,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
variant="ghost"
onClick={() => {
if (!params.id) {
- setStore("pendingAutoAccept", (value) => !value)
+ permission.toggleAutoAcceptDirectory(sdk.directory)
return
}
permission.toggleAutoAccept(params.id, sdk.directory)
diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts
index 2e4cf4faf..755611300 100644
--- a/packages/app/src/context/permission-auto-respond.test.ts
+++ b/packages/app/src/context/permission-auto-respond.test.ts
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test"
import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client"
import { base64Encode } from "@opencode-ai/util/encode"
-import { autoRespondsPermission } from "./permission-auto-respond"
+import { autoRespondsPermission, isDirectoryAutoAccepting } from "./permission-auto-respond"
const session = (input: { id: string; parentID?: string }) =>
({
@@ -60,4 +60,43 @@ describe("autoRespondsPermission", () => {
expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(true)
})
+
+ test("falls back to directory-level auto-accept", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/*`]: true,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("root"), directory)).toBe(true)
+ })
+
+ test("session-level override takes precedence over directory-level", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/*`]: true,
+ [`${base64Encode(directory)}/root`]: false,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("root"), directory)).toBe(false)
+ })
+})
+
+describe("isDirectoryAutoAccepting", () => {
+ test("returns true when directory key is set", () => {
+ const directory = "/tmp/project"
+ const autoAccept = { [`${base64Encode(directory)}/*`]: true }
+ expect(isDirectoryAutoAccepting(autoAccept, directory)).toBe(true)
+ })
+
+ test("returns false when directory key is not set", () => {
+ expect(isDirectoryAutoAccepting({}, "/tmp/project")).toBe(false)
+ })
+
+ test("returns false when directory key is explicitly false", () => {
+ const directory = "/tmp/project"
+ const autoAccept = { [`${base64Encode(directory)}/*`]: false }
+ expect(isDirectoryAutoAccepting(autoAccept, directory)).toBe(false)
+ })
})
diff --git a/packages/app/src/context/permission-auto-respond.ts b/packages/app/src/context/permission-auto-respond.ts
index 727ccc937..b206deedf 100644
--- a/packages/app/src/context/permission-auto-respond.ts
+++ b/packages/app/src/context/permission-auto-respond.ts
@@ -5,9 +5,19 @@ export function acceptKey(sessionID: string, directory?: string) {
return `${base64Encode(directory)}/${sessionID}`
}
+export function directoryAcceptKey(directory: string) {
+ return `${base64Encode(directory)}/*`
+}
+
function accepted(autoAccept: Record<string, boolean>, sessionID: string, directory?: string) {
const key = acceptKey(sessionID, directory)
- return autoAccept[key] ?? autoAccept[sessionID]
+ const directoryKey = directory ? directoryAcceptKey(directory) : undefined
+ return autoAccept[key] ?? autoAccept[sessionID] ?? (directoryKey ? autoAccept[directoryKey] : undefined)
+}
+
+export function isDirectoryAutoAccepting(autoAccept: Record<string, boolean>, directory: string) {
+ const key = directoryAcceptKey(directory)
+ return autoAccept[key] ?? false
}
function sessionLineage(session: { id: string; parentID?: string }[], sessionID: string) {
diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx
index 73ee08c9a..8208e46b9 100644
--- a/packages/app/src/context/permission.tsx
+++ b/packages/app/src/context/permission.tsx
@@ -1,4 +1,4 @@
-import { createMemo, onCleanup } from "solid-js"
+import { createEffect, createMemo, onCleanup } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import type { PermissionRequest } from "@opencode-ai/sdk/v2/client"
@@ -7,7 +7,7 @@ import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "./global-sync"
import { useParams } from "@solidjs/router"
import { decode64 } from "@/utils/base64"
-import { acceptKey, autoRespondsPermission } from "./permission-auto-respond"
+import { acceptKey, directoryAcceptKey, isDirectoryAutoAccepting, autoRespondsPermission } from "./permission-auto-respond"
type PermissionRespondFn = (input: {
sessionID: string
@@ -76,6 +76,25 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
}),
)
+ // When config has permission: "allow", auto-enable directory-level auto-accept
+ createEffect(() => {
+ if (!ready()) return
+ const directory = decode64(params.dir)
+ if (!directory) return
+ const [childStore] = globalSync.child(directory)
+ const perm = childStore.config.permission
+ if (typeof perm === "string" && perm === "allow") {
+ const key = directoryAcceptKey(directory)
+ if (store.autoAccept[key] === undefined) {
+ setStore(
+ produce((draft) => {
+ draft.autoAccept[key] = true
+ }),
+ )
+ }
+ }
+ })
+
const MAX_RESPONDED = 1000
const RESPONDED_TTL_MS = 60 * 60 * 1000
const responded = new Map<string, number>()
@@ -119,6 +138,10 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
}
+ function isAutoAcceptingDirectory(directory: string) {
+ return isDirectoryAutoAccepting(store.autoAccept, directory)
+ }
+
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
return autoRespondsPermission(store.autoAccept, session, permission, directory)
@@ -142,6 +165,36 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
})
onCleanup(unsubscribe)
+ function enableDirectory(directory: string) {
+ const key = directoryAcceptKey(directory)
+ setStore(
+ produce((draft) => {
+ draft.autoAccept[key] = true
+ }),
+ )
+
+ globalSDK.client.permission
+ .list({ directory })
+ .then((x) => {
+ if (!isAutoAcceptingDirectory(directory)) return
+ for (const perm of x.data ?? []) {
+ if (!perm?.id) continue
+ if (!shouldAutoRespond(perm, directory)) continue
+ respondOnce(perm, directory)
+ }
+ })
+ .catch(() => undefined)
+ }
+
+ function disableDirectory(directory: string) {
+ const key = directoryAcceptKey(directory)
+ setStore(
+ produce((draft) => {
+ draft.autoAccept[key] = false
+ }),
+ )
+ }
+
function enable(sessionID: string, directory: string) {
const key = acceptKey(sessionID, directory)
const version = bumpEnableVersion(sessionID, directory)
@@ -185,6 +238,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
return shouldAutoRespond(permission, directory)
},
isAutoAccepting,
+ isAutoAcceptingDirectory,
toggleAutoAccept(sessionID: string, directory: string) {
if (isAutoAccepting(sessionID, directory)) {
disable(sessionID, directory)
@@ -193,6 +247,13 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
enable(sessionID, directory)
},
+ toggleAutoAcceptDirectory(directory: string) {
+ if (isAutoAcceptingDirectory(directory)) {
+ disableDirectory(directory)
+ return
+ }
+ enableDirectory(directory)
+ },
enableAutoAccept(sessionID: string, directory: string) {
if (isAutoAccepting(sessionID, directory)) return
enable(sessionID, directory)
@@ -201,6 +262,11 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
disable(sessionID, directory)
},
permissionsEnabled,
+ isPermissionAllowAll(directory: string) {
+ const [childStore] = globalSync.child(directory)
+ const perm = childStore.config.permission
+ return typeof perm === "string" && perm === "allow"
+ },
}
},
})
diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx
index 461351878..b8ddeda82 100644
--- a/packages/app/src/pages/session/use-session-commands.tsx
+++ b/packages/app/src/pages/session/use-session-commands.tsx
@@ -261,24 +261,35 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
}),
])
+ const isAutoAcceptActive = () => {
+ const sessionID = params.id
+ if (sessionID) return permission.isAutoAccepting(sessionID, sdk.directory)
+ return permission.isAutoAcceptingDirectory(sdk.directory)
+ }
+
const permissionCommands = createMemo(() => [
permissionsCommand({
id: "permissions.autoaccept",
- title:
- params.id && permission.isAutoAccepting(params.id, sdk.directory)
- ? language.t("command.permissions.autoaccept.disable")
- : language.t("command.permissions.autoaccept.enable"),
+ title: isAutoAcceptActive()
+ ? language.t("command.permissions.autoaccept.disable")
+ : language.t("command.permissions.autoaccept.enable"),
keybind: "mod+shift+a",
- disabled: !params.id || !permission.permissionsEnabled(),
+ disabled: false,
onSelect: () => {
const sessionID = params.id
- if (!sessionID) return
- permission.toggleAutoAccept(sessionID, sdk.directory)
+ if (sessionID) {
+ permission.toggleAutoAccept(sessionID, sdk.directory)
+ } else {
+ permission.toggleAutoAcceptDirectory(sdk.directory)
+ }
+ const active = sessionID
+ ? permission.isAutoAccepting(sessionID, sdk.directory)
+ : permission.isAutoAcceptingDirectory(sdk.directory)
showToast({
- title: permission.isAutoAccepting(sessionID, sdk.directory)
+ title: active
? language.t("toast.permissions.autoaccept.on.title")
: language.t("toast.permissions.autoaccept.off.title"),
- description: permission.isAutoAccepting(sessionID, sdk.directory)
+ description: active
? language.t("toast.permissions.autoaccept.on.description")
: language.t("toast.permissions.autoaccept.off.description"),
})
diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts
index 34e80d71a..b5d6e8adf 100755
--- a/packages/opencode/script/build.ts
+++ b/packages/opencode/script/build.ts
@@ -4,7 +4,7 @@ import { $ } from "bun"
import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"
-import solidPlugin from "../node_modules/@opentui/solid/scripts/solid-plugin"
+import solidPlugin from "@opentui/solid/bun-plugin"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
@@ -161,7 +161,9 @@ for (const item of targets) {
console.log(`building ${name}`)
await $`mkdir -p dist/${name}/bin`
- const parserWorker = fs.realpathSync(path.resolve(dir, "./node_modules/@opentui/core/parser.worker.js"))
+ const localPath = path.resolve(dir, "node_modules/@opentui/core/parser.worker.js")
+ const rootPath = path.resolve(dir, "../../node_modules/@opentui/core/parser.worker.js")
+ const parserWorker = fs.realpathSync(fs.existsSync(localPath) ? localPath : rootPath)
const workerPath = "./src/cli/cmd/tui/worker.ts"
// Use platform-specific bunfs root path based on target OS