summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-27 05:16:39 -0600
committerAdam <[email protected]>2025-12-27 14:43:42 -0600
commit21eba5f987482b4e2e75ab1c564815bd7b0613f4 (patch)
tree2d8cad03e54baa29d83e1e835a7ef2e64d3897e4 /packages/app/src/pages
parentc523ca412747d66e0236865a4fa2481f7d50f64e (diff)
downloadopencode-21eba5f987482b4e2e75ab1c564815bd7b0613f4.tar.gz
opencode-21eba5f987482b4e2e75ab1c564815bd7b0613f4.zip
feat(desktop): permissions
Diffstat (limited to 'packages/app/src/pages')
-rw-r--r--packages/app/src/pages/directory-layout.tsx11
-rw-r--r--packages/app/src/pages/layout.tsx50
2 files changed, 58 insertions, 3 deletions
diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx
index c909a373d..04f90bdcb 100644
--- a/packages/app/src/pages/directory-layout.tsx
+++ b/packages/app/src/pages/directory-layout.tsx
@@ -1,6 +1,6 @@
import { createMemo, Show, type ParentProps } from "solid-js"
import { useParams } from "@solidjs/router"
-import { SDKProvider } from "@/context/sdk"
+import { SDKProvider, useSDK } from "@/context/sdk"
import { SyncProvider, useSync } from "@/context/sync"
import { LocalProvider } from "@/context/local"
import { base64Decode } from "@opencode-ai/util/encode"
@@ -18,8 +18,15 @@ export default function Layout(props: ParentProps) {
<SyncProvider>
{iife(() => {
const sync = useSync()
+ const sdk = useSDK()
return (
- <DataProvider data={sync.data} directory={directory()}>
+ <DataProvider
+ data={sync.data}
+ directory={directory()}
+ onPermissionRespond={(input) => {
+ sdk.client.permission.respond(input)
+ }}
+ >
<LocalProvider>{props.children}</LocalProvider>
</DataProvider>
)
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 5efba6d99..538a3b840 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -117,6 +117,39 @@ export default function Layout(props: ParentProps) {
}
})
+ onMount(() => {
+ const unsub = globalSync.permission.onUpdated(({ directory, permission }) => {
+ const currentDir = params.dir ? base64Decode(params.dir) : undefined
+ const currentSession = params.id
+ if (directory === currentDir && permission.sessionID === currentSession) return
+ const [store] = globalSync.child(directory)
+ const session = store.session.find((s) => s.id === permission.sessionID)
+ if (directory === currentDir && session?.parentID === currentSession) return
+ const sessionTitle = session?.title ?? "New session"
+ const projectName = getFilename(directory)
+ showToast({
+ persistent: true,
+ icon: "checklist",
+ title: "Permission required",
+ description: `${sessionTitle} in ${projectName} needs permission`,
+ actions: [
+ {
+ label: "Go to session",
+ onClick: () => {
+ navigate(`/${base64Encode(directory)}/session/${permission.sessionID}`)
+ },
+ dismissAfter: true,
+ },
+ {
+ label: "Dismiss",
+ onClick: "dismiss",
+ },
+ ],
+ })
+ })
+ onCleanup(unsub)
+ })
+
function sortSessions(a: Session, b: Session) {
const now = Date.now()
const oneMinuteAgo = now - 60 * 1000
@@ -454,8 +487,20 @@ export default function Layout(props: ParentProps) {
const updated = createMemo(() => DateTime.fromMillis(props.session.time.updated))
const notifications = createMemo(() => notification.session.unseen(props.session.id))
const hasError = createMemo(() => notifications().some((n) => n.type === "error"))
+ const hasPermissions = createMemo(() => {
+ const store = globalSync.child(props.project.worktree)[0]
+ const permissions = store.permission?.[props.session.id] ?? []
+ if (permissions.length > 0) return true
+ const childSessions = store.session.filter((s) => s.parentID === props.session.id)
+ for (const child of childSessions) {
+ const childPermissions = store.permission?.[child.id] ?? []
+ if (childPermissions.length > 0) return true
+ }
+ return false
+ })
const isWorking = createMemo(() => {
if (props.session.id === params.id) return false
+ if (hasPermissions()) return false
const status = globalSync.child(props.project.worktree)[0].session_status[props.session.id]
return status?.type === "busy" || status?.type === "retry"
})
@@ -486,6 +531,9 @@ export default function Layout(props: ParentProps) {
<Match when={isWorking()}>
<Spinner class="size-2.5 mr-0.5" />
</Match>
+ <Match when={hasPermissions()}>
+ <div class="size-1.5 mr-1.5 rounded-full bg-surface-warning-strong" />
+ </Match>
<Match when={hasError()}>
<div class="size-1.5 mr-1.5 rounded-full bg-text-diff-delete-base" />
</Match>
@@ -587,7 +635,7 @@ export default function Layout(props: ParentProps) {
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Item onSelect={() => closeProject(props.project.worktree)}>
- <DropdownMenu.ItemLabel>Close Project</DropdownMenu.ItemLabel>
+ <DropdownMenu.ItemLabel>Close project</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>