Просмотр исходного кода

fix(app): auto-accept permissions

Adam 1 месяц назад
Родитель
Сommit
dfa0281117

+ 37 - 35
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"}>

+ 23 - 2
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)
   })
 })

+ 9 - 4
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
 }

+ 5 - 4
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]
         }),
       )