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

fix(desktop): more fine-grained state updates for permissions

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

+ 44 - 21
packages/app/src/context/global-sync.tsx

@@ -23,7 +23,7 @@ import { Binary } from "@opencode-ai/util/binary"
 import { retry } from "@opencode-ai/util/retry"
 import { useGlobalSDK } from "./global-sdk"
 import { ErrorPage, type InitError } from "../pages/error"
-import { createContext, useContext, onMount, type ParentProps, Switch, Match } from "solid-js"
+import { batch, createContext, useContext, onMount, type ParentProps, Switch, Match } from "solid-js"
 import { showToast } from "@opencode-ai/ui/toast"
 import { getFilename } from "@opencode-ai/util/path"
 
@@ -135,7 +135,7 @@ function createGlobalSync() {
 
   async function bootstrapInstance(directory: string) {
     if (!directory) return
-    const [, setStore] = child(directory)
+    const [store, setStore] = child(directory)
     const sdk = createOpencodeClient({
       baseUrl: globalSDK.url,
       directory,
@@ -167,12 +167,32 @@ function createGlobalSync() {
       vcs: () => sdk.vcs.get().then((x) => setStore("vcs", x.data)),
       permission: () =>
         sdk.permission.list().then((x) => {
-          const grouped: Record<string, typeof x.data> = {}
+          const grouped: Record<string, Permission[]> = {}
           for (const perm of x.data ?? []) {
-            grouped[perm.sessionID] = grouped[perm.sessionID] ?? []
-            grouped[perm.sessionID]!.push(perm)
+            const existing = grouped[perm.sessionID]
+            if (existing) {
+              existing.push(perm)
+              continue
+            }
+            grouped[perm.sessionID] = [perm]
           }
-          setStore("permission", grouped)
+
+          batch(() => {
+            for (const sessionID of Object.keys(store.permission)) {
+              if (grouped[sessionID]) continue
+              setStore("permission", sessionID, [])
+            }
+            for (const [sessionID, permissions] of Object.entries(grouped)) {
+              setStore(
+                "permission",
+                sessionID,
+                reconcile(
+                  permissions.slice().sort((a, b) => a.id.localeCompare(b.id)),
+                  { key: "id" },
+                ),
+              )
+            }
+          })
         }),
     }
     await Promise.all(Object.values(load).map((p) => retry(p).catch((e) => setGlobalStore("error", e))))
@@ -325,23 +345,26 @@ function createGlobalSync() {
         break
       }
       case "permission.updated": {
-        const permissions = store.permission[event.properties.sessionID]
+        const sessionID = event.properties.sessionID
+        const permissions = store.permission[sessionID]
         if (!permissions) {
-          setStore("permission", event.properties.sessionID, [event.properties])
-        } else {
-          const result = Binary.search(permissions, event.properties.id, (p) => p.id)
-          setStore(
-            "permission",
-            event.properties.sessionID,
-            produce((draft) => {
-              if (result.found) {
-                draft[result.index] = event.properties
-                return
-              }
-              draft.push(event.properties)
-            }),
-          )
+          setStore("permission", sessionID, [event.properties])
+          break
         }
+
+        const result = Binary.search(permissions, event.properties.id, (p) => p.id)
+        if (result.found) {
+          setStore("permission", sessionID, result.index, reconcile(event.properties))
+          break
+        }
+
+        setStore(
+          "permission",
+          sessionID,
+          produce((draft) => {
+            draft.splice(result.index, 0, event.properties)
+          }),
+        )
         break
       }
       case "permission.replied": {

+ 8 - 1
packages/ui/src/components/message-part.tsx

@@ -623,7 +623,14 @@ ToolRegistry.register({
       const sessionId = childSessionId()
       if (!sessionId) return undefined
       const permissions = data.store.permission?.[sessionId] ?? []
-      return permissions.toSorted((a, b) => a.id.localeCompare(b.id))[0]
+      return permissions.reduce(
+        (result, perm) => {
+          if (!result) return perm
+          if (perm.id < result.id) return perm
+          return result
+        },
+        undefined as (typeof permissions)[number] | undefined,
+      )
     })
 
     const childToolPart = createMemo(() => {

+ 15 - 8
packages/ui/src/components/session-turn.tsx

@@ -4,7 +4,7 @@ import { useDiffComponent } from "../context/diff"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { checksum } from "@opencode-ai/util/encode"
 import { Binary } from "@opencode-ai/util/binary"
-import { createEffect, createMemo, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js"
+import { createEffect, createMemo, For, Match, on, onCleanup, ParentProps, Show, Switch } from "solid-js"
 import { createResizeObserver } from "@solid-primitives/resize-observer"
 import { DiffChanges } from "./diff-changes"
 import { Typewriter } from "./typewriter"
@@ -193,11 +193,16 @@ export function SessionTurn(
     return false
   })
 
+  const permissions = createMemo(() => data.store.permission?.[props.sessionID] ?? [])
+  const permissionCount = createMemo(() => permissions().length)
+
   const permissionParts = createMemo(() => {
-    const permissions = data.store.permission?.[props.sessionID] ?? []
-    if (!permissions.length) return [] as { part: ToolPart; message: AssistantMessage }[]
+    if (props.stepsExpanded) return [] as { part: ToolPart; message: AssistantMessage }[]
+
+    const items = permissions()
+    if (!items.length) return [] as { part: ToolPart; message: AssistantMessage }[]
 
-    const ids = new Set(permissions.map((perm) => perm.callID))
+    const ids = new Set(items.map((perm) => perm.callID))
     const result: { part: ToolPart; message: AssistantMessage }[] = []
 
     for (const message of assistantMessages()) {
@@ -371,11 +376,13 @@ export function SessionTurn(
     }
   })
 
-  createEffect(() => {
-    if (permissionParts().length > 0) {
+  createEffect(
+    on(permissionCount, (count, prev) => {
+      if (!count) return
+      if (prev !== undefined && count <= prev) return
       autoScroll.forceScrollToBottom()
-    }
-  })
+    }),
+  )
 
   createEffect(() => {
     if (working() || !isLastUserMessage()) return