summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-27 06:17:40 -0600
committerAdam <[email protected]>2026-02-27 06:17:40 -0600
commitdfa02811178af1af602f59f8cdbcb3f3e319f103 (patch)
treebb4e088da85d590a66b62fe5f1278a97af203e52
parent4a940969947654deca40454503491793ce1b347c (diff)
downloadopencode-dfa02811178af1af602f59f8cdbcb3f3e319f103.tar.gz
opencode-dfa02811178af1af602f59f8cdbcb3f3e319f103.zip
fix(app): auto-accept permissions
-rw-r--r--packages/app/src/components/prompt-input.tsx72
-rw-r--r--packages/app/src/context/permission-auto-respond.test.ts25
-rw-r--r--packages/app/src/context/permission-auto-respond.ts13
-rw-r--r--packages/app/src/context/permission.tsx9
4 files changed, 74 insertions, 45 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 85aa16384..3ba3763b8 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -1310,43 +1310,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</div>
</div>
- <Show when={store.mode === "normal" && permission.permissionsEnabled() && params.id}>
- <div class="pointer-events-none absolute bottom-2 left-2">
- <div class="pointer-events-auto">
- <TooltipKeybind
- placement="top"
- gutter={8}
- title={language.t(
- accepting() ? "command.permissions.autoaccept.disable" : "command.permissions.autoaccept.enable",
- )}
- keybind={command.keybind("permissions.autoaccept")}
+ <div class="pointer-events-none absolute bottom-2 left-2">
+ <div class="pointer-events-auto">
+ <TooltipKeybind
+ placement="top"
+ gutter={8}
+ title={language.t(
+ accepting() ? "command.permissions.autoaccept.disable" : "command.permissions.autoaccept.enable",
+ )}
+ keybind={command.keybind("permissions.autoaccept")}
+ >
+ <Button
+ data-action="prompt-permissions"
+ variant="ghost"
+ disabled={!params.id}
+ onClick={() => {
+ if (!params.id) return
+ permission.toggleAutoAccept(params.id, sdk.directory)
+ }}
+ classList={{
+ "size-6 flex items-center justify-center": true,
+ "text-text-base": !accepting(),
+ "hover:bg-surface-success-base": accepting(),
+ }}
+ aria-label={
+ accepting()
+ ? language.t("command.permissions.autoaccept.disable")
+ : language.t("command.permissions.autoaccept.enable")
+ }
+ aria-pressed={accepting()}
>
- <Button
- data-action="prompt-permissions"
- variant="ghost"
- onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)}
- classList={{
- "_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
- "text-text-base": !accepting(),
- "hover:bg-surface-success-base": accepting(),
- }}
- aria-label={
- accepting()
- ? language.t("command.permissions.autoaccept.disable")
- : language.t("command.permissions.autoaccept.enable")
- }
- aria-pressed={accepting()}
- >
- <Icon
- name="chevron-double-right"
- size="small"
- classList={{ "text-icon-success-base": accepting() }}
- />
- </Button>
- </TooltipKeybind>
- </div>
+ <Icon
+ name="chevron-double-right"
+ size="small"
+ classList={{ "text-icon-success-base": accepting() }}
+ />
+ </Button>
+ </TooltipKeybind>
</div>
- </Show>
+ </div>
</div>
</DockShellForm>
<Show when={store.mode === "normal" || store.mode === "shell"}>
diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts
index 1fa1ff3de..8657427d7 100644
--- a/packages/app/src/context/permission-auto-respond.test.ts
+++ b/packages/app/src/context/permission-auto-respond.test.ts
@@ -31,12 +31,33 @@ describe("autoRespondsPermission", () => {
expect(autoRespondsPermission({ root: true }, sessions, permission("child"), "/tmp/project")).toBe(true)
})
- test("ignores auto-accept from unrelated sessions", () => {
+ test("defaults to auto-accept when no lineage override exists", () => {
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" }), session({ id: "other" })]
const autoAccept = {
other: true,
}
- expect(autoRespondsPermission(autoAccept, sessions, permission("child"), "/tmp/project")).toBe(false)
+ expect(autoRespondsPermission(autoAccept, sessions, permission("child"), "/tmp/project")).toBe(true)
+ })
+
+ test("inherits a parent session's false override", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/root`]: false,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(false)
+ })
+
+ test("prefers a child override over parent override", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/root`]: false,
+ [`${base64Encode(directory)}/child`]: true,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(true)
})
})
diff --git a/packages/app/src/context/permission-auto-respond.ts b/packages/app/src/context/permission-auto-respond.ts
index e45e5f51c..cabd514e7 100644
--- a/packages/app/src/context/permission-auto-respond.ts
+++ b/packages/app/src/context/permission-auto-respond.ts
@@ -5,6 +5,11 @@ export function acceptKey(sessionID: string, directory?: string) {
return `${base64Encode(directory)}/${sessionID}`
}
+function accepted(autoAccept: Record<string, boolean>, sessionID: string, directory?: string) {
+ const key = acceptKey(sessionID, directory)
+ return autoAccept[key] ?? autoAccept[sessionID]
+}
+
function sessionLineage(session: { id: string; parentID?: string }[], sessionID: string) {
const parent = session.reduce((acc, item) => {
if (item.parentID) acc.set(item.id, item.parentID)
@@ -29,8 +34,8 @@ export function autoRespondsPermission(
permission: { sessionID: string },
directory?: string,
) {
- return sessionLineage(session, permission.sessionID).some((id) => {
- const key = acceptKey(id, directory)
- return autoAccept[key] ?? autoAccept[id] ?? false
- })
+ const value = sessionLineage(session, permission.sessionID)
+ .map((id) => accepted(autoAccept, id, directory))
+ .find((item): item is boolean => item !== undefined)
+ return value ?? true
}
diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx
index d63d4d568..73ee08c9a 100644
--- a/packages/app/src/context/permission.tsx
+++ b/packages/app/src/context/permission.tsx
@@ -115,8 +115,8 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
}
function isAutoAccepting(sessionID: string, directory?: string) {
- const key = acceptKey(sessionID, directory)
- return store.autoAccept[key] ?? store.autoAccept[sessionID] ?? false
+ const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
+ return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
}
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
@@ -168,10 +168,11 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
function disable(sessionID: string, directory?: string) {
bumpEnableVersion(sessionID, directory)
- const key = directory ? acceptKey(sessionID, directory) : undefined
+ const key = directory ? acceptKey(sessionID, directory) : sessionID
setStore(
produce((draft) => {
- if (key) delete draft.autoAccept[key]
+ draft.autoAccept[key] = false
+ if (!directory) return
delete draft.autoAccept[sessionID]
}),
)