summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 08:47:27 -0600
committerAdam <[email protected]>2025-12-29 08:47:38 -0600
commitc4930eb6b219a2011cddc70a9b76f41809418114 (patch)
treece90fdd5b21622fcb8b1a3b851e0842d771ac112
parenta24549fce77b12f025f0583063df2c7f23ad3ffc (diff)
downloadopencode-c4930eb6b219a2011cddc70a9b76f41809418114.tar.gz
opencode-c4930eb6b219a2011cddc70a9b76f41809418114.zip
fix(desktop): more fine-grained state updates for permissions
-rw-r--r--packages/app/src/context/global-sync.tsx65
-rw-r--r--packages/ui/src/components/message-part.tsx9
-rw-r--r--packages/ui/src/components/session-turn.tsx23
3 files changed, 67 insertions, 30 deletions
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index 0296dfad3..e1dcf15fc 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/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": {
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 2d39207ec..83811494c 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/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(() => {
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 8e9aa9f23..e1a392bf0 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/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