fix(app): auto-accept permissions

This commit is contained in:
Adam
2026-02-27 06:17:40 -06:00
parent 4a94096994
commit dfa0281117
4 changed files with 74 additions and 45 deletions

View File

@@ -1310,7 +1310,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</div> </div>
</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-none absolute bottom-2 left-2">
<div class="pointer-events-auto"> <div class="pointer-events-auto">
<TooltipKeybind <TooltipKeybind
@@ -1324,9 +1323,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Button <Button
data-action="prompt-permissions" data-action="prompt-permissions"
variant="ghost" variant="ghost"
onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)} disabled={!params.id}
onClick={() => {
if (!params.id) return
permission.toggleAutoAccept(params.id, sdk.directory)
}}
classList={{ classList={{
"_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true, "size-6 flex items-center justify-center": true,
"text-text-base": !accepting(), "text-text-base": !accepting(),
"hover:bg-surface-success-base": accepting(), "hover:bg-surface-success-base": accepting(),
}} }}
@@ -1346,7 +1349,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</TooltipKeybind> </TooltipKeybind>
</div> </div>
</div> </div>
</Show>
</div> </div>
</DockShellForm> </DockShellForm>
<Show when={store.mode === "normal" || store.mode === "shell"}> <Show when={store.mode === "normal" || store.mode === "shell"}>

View File

@@ -31,12 +31,33 @@ describe("autoRespondsPermission", () => {
expect(autoRespondsPermission({ root: true }, sessions, permission("child"), "/tmp/project")).toBe(true) 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 sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" }), session({ id: "other" })]
const autoAccept = { const autoAccept = {
other: true, 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)
}) })
}) })

View File

@@ -5,6 +5,11 @@ export function acceptKey(sessionID: string, directory?: string) {
return `${base64Encode(directory)}/${sessionID}` 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) { function sessionLineage(session: { id: string; parentID?: string }[], sessionID: string) {
const parent = session.reduce((acc, item) => { const parent = session.reduce((acc, item) => {
if (item.parentID) acc.set(item.id, item.parentID) if (item.parentID) acc.set(item.id, item.parentID)
@@ -29,8 +34,8 @@ export function autoRespondsPermission(
permission: { sessionID: string }, permission: { sessionID: string },
directory?: string, directory?: string,
) { ) {
return sessionLineage(session, permission.sessionID).some((id) => { const value = sessionLineage(session, permission.sessionID)
const key = acceptKey(id, directory) .map((id) => accepted(autoAccept, id, directory))
return autoAccept[key] ?? autoAccept[id] ?? false .find((item): item is boolean => item !== undefined)
}) return value ?? true
} }

View File

@@ -115,8 +115,8 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
} }
function isAutoAccepting(sessionID: string, directory?: string) { function isAutoAccepting(sessionID: string, directory?: string) {
const key = acceptKey(sessionID, directory) const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
return store.autoAccept[key] ?? store.autoAccept[sessionID] ?? false return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
} }
function shouldAutoRespond(permission: PermissionRequest, directory?: string) { function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
@@ -168,10 +168,11 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
function disable(sessionID: string, directory?: string) { function disable(sessionID: string, directory?: string) {
bumpEnableVersion(sessionID, directory) bumpEnableVersion(sessionID, directory)
const key = directory ? acceptKey(sessionID, directory) : undefined const key = directory ? acceptKey(sessionID, directory) : sessionID
setStore( setStore(
produce((draft) => { produce((draft) => {
if (key) delete draft.autoAccept[key] draft.autoAccept[key] = false
if (!directory) return
delete draft.autoAccept[sessionID] delete draft.autoAccept[sessionID]
}), }),
) )