Buckets:
| import { createEffect, createMemo, onCleanup } from "solid-js" | |
| import { createStore, produce } from "solid-js/store" | |
| import { createSimpleContext } from "@opencode-ai/ui/context" | |
| import type { PermissionRequest } from "@opencode-ai/sdk/v2/client" | |
| import { Persist, persisted } from "@/utils/persist" | |
| import { useServerSDK } from "@/context/server-sdk" | |
| import { useServerSync } from "./server-sync" | |
| import { useParams } from "@solidjs/router" | |
| import { decode64 } from "@/utils/base64" | |
| import { | |
| acceptKey, | |
| directoryAcceptKey, | |
| isDirectoryAutoAccepting, | |
| autoRespondsPermission, | |
| } from "./permission-auto-respond" | |
| type PermissionRespondFn = (input: { | |
| sessionID: string | |
| permissionID: string | |
| response: "once" | "always" | "reject" | |
| directory?: string | |
| }) => void | |
| function isNonAllowRule(rule: unknown) { | |
| if (!rule) return false | |
| if (typeof rule === "string") return rule !== "allow" | |
| if (typeof rule !== "object") return false | |
| if (Array.isArray(rule)) return false | |
| for (const action of Object.values(rule)) { | |
| if (action !== "allow") return true | |
| } | |
| return false | |
| } | |
| function hasPermissionPromptRules(permission: unknown) { | |
| if (!permission) return false | |
| if (typeof permission === "string") return permission !== "allow" | |
| if (typeof permission !== "object") return false | |
| if (Array.isArray(permission)) return false | |
| const config = permission as Record<string, unknown> | |
| return Object.values(config).some(isNonAllowRule) | |
| } | |
| export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({ | |
| name: "Permission", | |
| gate: false, | |
| init: () => { | |
| const params = useParams() | |
| const serverSDK = useServerSDK() | |
| const serverSync = useServerSync() | |
| const permissionsEnabled = createMemo(() => { | |
| const directory = decode64(params.dir) | |
| if (!directory) return false | |
| const [store] = serverSync.child(directory) | |
| return hasPermissionPromptRules(store.config.permission) | |
| }) | |
| const [store, setStore, _, ready] = persisted( | |
| { | |
| ...Persist.serverGlobal(serverSDK.scope, "permission", ["permission.v3"]), | |
| migrate(value) { | |
| if (!value || typeof value !== "object" || Array.isArray(value)) return value | |
| const data = value as Record<string, unknown> | |
| if (data.autoAccept) return value | |
| return { | |
| ...data, | |
| autoAccept: | |
| typeof data.autoAcceptEdits === "object" && data.autoAcceptEdits && !Array.isArray(data.autoAcceptEdits) | |
| ? data.autoAcceptEdits | |
| : {}, | |
| } | |
| }, | |
| }, | |
| createStore({ | |
| autoAccept: {} as Record<string, boolean>, | |
| }), | |
| ) | |
| // When config has permission: "allow", auto-enable directory-level auto-accept | |
| createEffect(() => { | |
| if (!ready()) return | |
| const directory = decode64(params.dir) | |
| if (!directory) return | |
| const [childStore] = serverSync.child(directory) | |
| const perm = childStore.config.permission | |
| if (typeof perm === "string" && perm === "allow") { | |
| const key = directoryAcceptKey(directory) | |
| if (store.autoAccept[key] === undefined) { | |
| setStore( | |
| produce((draft) => { | |
| draft.autoAccept[key] = true | |
| }), | |
| ) | |
| } | |
| } | |
| }) | |
| const MAX_RESPONDED = 1000 | |
| const RESPONDED_TTL_MS = 60 * 60 * 1000 | |
| const responded = new Map<string, number>() | |
| const enableVersion = new Map<string, number>() | |
| function pruneResponded(now: number) { | |
| for (const [id, ts] of responded) { | |
| if (now - ts < RESPONDED_TTL_MS) break | |
| responded.delete(id) | |
| } | |
| for (const id of responded.keys()) { | |
| if (responded.size <= MAX_RESPONDED) break | |
| responded.delete(id) | |
| } | |
| } | |
| const respond: PermissionRespondFn = (input) => { | |
| serverSDK.client.permission.respond(input).catch(() => { | |
| responded.delete(input.permissionID) | |
| }) | |
| } | |
| function respondOnce(permission: PermissionRequest, directory?: string) { | |
| const now = Date.now() | |
| const hit = responded.has(permission.id) | |
| responded.delete(permission.id) | |
| responded.set(permission.id, now) | |
| pruneResponded(now) | |
| if (hit) return | |
| respond({ | |
| sessionID: permission.sessionID, | |
| permissionID: permission.id, | |
| response: "once", | |
| directory, | |
| }) | |
| } | |
| function isAutoAccepting(sessionID: string, directory?: string) { | |
| const session = directory ? serverSync.child(directory, { bootstrap: false })[0].session : [] | |
| return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory) | |
| } | |
| function isAutoAcceptingDirectory(directory: string) { | |
| return isDirectoryAutoAccepting(store.autoAccept, directory) | |
| } | |
| function shouldAutoRespond(permission: PermissionRequest, directory?: string) { | |
| const session = directory ? serverSync.child(directory, { bootstrap: false })[0].session : [] | |
| return autoRespondsPermission(store.autoAccept, session, permission, directory) | |
| } | |
| function bumpEnableVersion(sessionID: string, directory?: string) { | |
| const key = acceptKey(sessionID, directory) | |
| const next = (enableVersion.get(key) ?? 0) + 1 | |
| enableVersion.set(key, next) | |
| return next | |
| } | |
| const unsubscribe = serverSDK.event.listen((e) => { | |
| const event = e.details | |
| if (event?.type !== "permission.asked") return | |
| const perm = event.properties | |
| if (!shouldAutoRespond(perm, e.name)) return | |
| respondOnce(perm, e.name) | |
| }) | |
| onCleanup(unsubscribe) | |
| function enableDirectory(directory: string) { | |
| const key = directoryAcceptKey(directory) | |
| setStore( | |
| produce((draft) => { | |
| draft.autoAccept[key] = true | |
| }), | |
| ) | |
| serverSDK.client.permission | |
| .list({ directory }) | |
| .then((x) => { | |
| if (!isAutoAcceptingDirectory(directory)) return | |
| for (const perm of x.data ?? []) { | |
| if (!perm?.id) continue | |
| if (!shouldAutoRespond(perm, directory)) continue | |
| respondOnce(perm, directory) | |
| } | |
| }) | |
| .catch(() => undefined) | |
| } | |
| function disableDirectory(directory: string) { | |
| const key = directoryAcceptKey(directory) | |
| setStore( | |
| produce((draft) => { | |
| draft.autoAccept[key] = false | |
| }), | |
| ) | |
| } | |
| function enable(sessionID: string, directory: string) { | |
| const key = acceptKey(sessionID, directory) | |
| const version = bumpEnableVersion(sessionID, directory) | |
| setStore( | |
| produce((draft) => { | |
| draft.autoAccept[key] = true | |
| delete draft.autoAccept[sessionID] | |
| }), | |
| ) | |
| serverSDK.client.permission | |
| .list({ directory }) | |
| .then((x) => { | |
| if (enableVersion.get(key) !== version) return | |
| if (!isAutoAccepting(sessionID, directory)) return | |
| for (const perm of x.data ?? []) { | |
| if (!perm?.id) continue | |
| if (!shouldAutoRespond(perm, directory)) continue | |
| respondOnce(perm, directory) | |
| } | |
| }) | |
| .catch(() => undefined) | |
| } | |
| function disable(sessionID: string, directory?: string) { | |
| bumpEnableVersion(sessionID, directory) | |
| const key = directory ? acceptKey(sessionID, directory) : sessionID | |
| setStore( | |
| produce((draft) => { | |
| draft.autoAccept[key] = false | |
| if (!directory) return | |
| delete draft.autoAccept[sessionID] | |
| }), | |
| ) | |
| } | |
| return { | |
| ready, | |
| respond, | |
| autoResponds(permission: PermissionRequest, directory?: string) { | |
| return shouldAutoRespond(permission, directory) | |
| }, | |
| isAutoAccepting, | |
| isAutoAcceptingDirectory, | |
| toggleAutoAccept(sessionID: string, directory: string) { | |
| if (isAutoAccepting(sessionID, directory)) { | |
| disable(sessionID, directory) | |
| return | |
| } | |
| enable(sessionID, directory) | |
| }, | |
| toggleAutoAcceptDirectory(directory: string) { | |
| if (isAutoAcceptingDirectory(directory)) { | |
| disableDirectory(directory) | |
| return | |
| } | |
| enableDirectory(directory) | |
| }, | |
| enableAutoAccept(sessionID: string, directory: string) { | |
| if (isAutoAccepting(sessionID, directory)) return | |
| enable(sessionID, directory) | |
| }, | |
| disableAutoAccept(sessionID: string, directory?: string) { | |
| disable(sessionID, directory) | |
| }, | |
| permissionsEnabled, | |
| isPermissionAllowAll(directory: string) { | |
| const [childStore] = serverSync.child(directory) | |
| const perm = childStore.config.permission | |
| return typeof perm === "string" && perm === "allow" | |
| }, | |
| } | |
| }, | |
| }) | |
Xet Storage Details
- Size:
- 8.77 kB
- Xet hash:
- 7840621159b2130089013ece6b6023bba2d987922f61b5caaa28860d8b9e4bfa
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.