summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 12:57:48 -0600
committerAdam <[email protected]>2025-12-29 14:23:41 -0600
commit31092149002eb69111f4b94a16f875e101c61432 (patch)
treee3826bb81c8cbdd667cbdc11ffa8cfc84b25a69d /packages/app/src/context
parent86ccc3409b1d3d5cca70c4aec52e5cf0792aa9dd (diff)
downloadopencode-31092149002eb69111f4b94a16f875e101c61432.tar.gz
opencode-31092149002eb69111f4b94a16f875e101c61432.zip
feat(desktop): auto-accept edits toggle
Diffstat (limited to 'packages/app/src/context')
-rw-r--r--packages/app/src/context/permission.tsx130
1 files changed, 130 insertions, 0 deletions
diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx
new file mode 100644
index 000000000..6d7b335ad
--- /dev/null
+++ b/packages/app/src/context/permission.tsx
@@ -0,0 +1,130 @@
+import { createEffect, createRoot, onCleanup } from "solid-js"
+import { createStore } from "solid-js/store"
+import { createSimpleContext } from "@opencode-ai/ui/context"
+import type { Permission } from "@opencode-ai/sdk/v2/client"
+import { persisted } from "@/utils/persist"
+
+type PermissionsBySession = {
+ [sessionID: string]: Permission[]
+}
+
+type PermissionRespondFn = (input: {
+ sessionID: string
+ permissionID: string
+ response: "once" | "always" | "reject"
+}) => void
+
+const AUTO_ACCEPT_TYPES = new Set(["edit", "write"])
+
+function shouldAutoAccept(perm: Permission) {
+ return AUTO_ACCEPT_TYPES.has(perm.type)
+}
+
+export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({
+ name: "Permission",
+ init: (props: { permissions: PermissionsBySession; onRespond: PermissionRespondFn }) => {
+ const [store, setStore, _, ready] = persisted(
+ "permission.v1",
+ createStore({
+ autoAcceptEdits: {} as Record<string, boolean>,
+ }),
+ )
+
+ const responded = new Set<string>()
+ const watches = new Map<string, () => void>()
+
+ function respond(perm: Permission) {
+ if (responded.has(perm.id)) return
+ responded.add(perm.id)
+ props.onRespond({
+ sessionID: perm.sessionID,
+ permissionID: perm.id,
+ response: "once",
+ })
+ }
+
+ function watch(sessionID: string) {
+ if (watches.has(sessionID)) return
+
+ const dispose = createRoot((dispose) => {
+ createEffect(() => {
+ if (!store.autoAcceptEdits[sessionID]) return
+
+ const permissions = props.permissions[sessionID] ?? []
+ permissions.length
+
+ for (const perm of permissions) {
+ if (!shouldAutoAccept(perm)) continue
+ respond(perm)
+ }
+ })
+
+ return dispose
+ })
+
+ watches.set(sessionID, dispose)
+ }
+
+ function unwatch(sessionID: string) {
+ const dispose = watches.get(sessionID)
+ if (!dispose) return
+ dispose()
+ watches.delete(sessionID)
+ }
+
+ createEffect(() => {
+ if (!ready()) return
+
+ for (const sessionID in store.autoAcceptEdits) {
+ if (!store.autoAcceptEdits[sessionID]) continue
+ watch(sessionID)
+ }
+ })
+
+ onCleanup(() => {
+ for (const dispose of watches.values()) dispose()
+ watches.clear()
+ })
+
+ function enable(sessionID: string) {
+ setStore("autoAcceptEdits", sessionID, true)
+ watch(sessionID)
+
+ const permissions = props.permissions[sessionID] ?? []
+ for (const perm of permissions) {
+ if (!shouldAutoAccept(perm)) continue
+ respond(perm)
+ }
+ }
+
+ function disable(sessionID: string) {
+ setStore("autoAcceptEdits", sessionID, false)
+ unwatch(sessionID)
+ }
+
+ return {
+ get permissions() {
+ return props.permissions
+ },
+ respond: props.onRespond,
+ isAutoAccepting(sessionID: string) {
+ return store.autoAcceptEdits[sessionID] ?? false
+ },
+ toggleAutoAccept(sessionID: string) {
+ if (store.autoAcceptEdits[sessionID]) {
+ disable(sessionID)
+ return
+ }
+
+ enable(sessionID)
+ },
+ enableAutoAccept(sessionID: string) {
+ if (store.autoAcceptEdits[sessionID]) return
+ enable(sessionID)
+ },
+ disableAutoAccept(sessionID: string) {
+ disable(sessionID)
+ },
+ }
+ },
+})