summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
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/app/src/context
parent6c7d968c4423a0cd6c85099c9377a6066313fa0a (diff)
downloadopencode-b7605add5803becb0a1abd9fb5110cb636cc6d01.tar.gz
opencode-b7605add5803becb0a1abd9fb5110cb636cc6d01.zip
fix(app): enable auto-accept keybind regardless of permission config (#16259)
Diffstat (limited to 'packages/app/src/context')
-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
3 files changed, 119 insertions, 4 deletions
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"
+ },
}
},
})