From 924fc9ed803d4dfa89faed65579a5a85cd7666c0 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:31:03 -0600 Subject: [PATCH] wip(app): settings --- packages/app/src/components/prompt-input.tsx | 1 - .../src/components/settings-permissions.tsx | 151 +++++++++++- packages/app/src/context/global-sync.tsx | 215 +++++++++++------- packages/opencode/src/config/config.ts | 108 ++++++++- packages/opencode/src/file/watcher.ts | 23 +- packages/opencode/src/server/event.ts | 7 + packages/opencode/src/server/server.ts | 5 - packages/sdk/js/src/v2/gen/sdk.gen.ts | 39 ++++ packages/sdk/js/src/v2/gen/types.gen.ts | 41 ++++ 9 files changed, 488 insertions(+), 102 deletions(-) create mode 100644 packages/opencode/src/server/event.ts diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 56bbdc8cb..072ef0bdd 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -255,7 +255,6 @@ export const PromptInput: Component = (props) => { createEffect(() => { params.id - editorRef.focus() if (params.id) return const interval = setInterval(() => { setStore("placeholder", (prev) => (prev + 1) % PLACEHOLDERS.length) diff --git a/packages/app/src/components/settings-permissions.tsx b/packages/app/src/components/settings-permissions.tsx index 67c3bfb62..f5ee76650 100644 --- a/packages/app/src/components/settings-permissions.tsx +++ b/packages/app/src/components/settings-permissions.tsx @@ -1,12 +1,153 @@ -import { Component } from "solid-js" +import { Select } from "@opencode-ai/ui/select" +import { showToast } from "@opencode-ai/ui/toast" +import { Component, For, createMemo, type JSX } from "solid-js" +import { useGlobalSync } from "@/context/global-sync" + +type PermissionAction = "allow" | "ask" | "deny" + +type PermissionObject = Record +type PermissionValue = PermissionAction | PermissionObject | string[] | undefined +type PermissionMap = Record + +type PermissionItem = { + id: string + title: string + description: string +} + +const ACTIONS: Array<{ value: PermissionAction; label: string }> = [ + { value: "allow", label: "Allow" }, + { value: "ask", label: "Ask" }, + { value: "deny", label: "Deny" }, +] + +const ITEMS: PermissionItem[] = [ + { id: "read", title: "Read", description: "Reading a file (matches the file path)" }, + { id: "edit", title: "Edit", description: "Modify files, including edits, writes, patches, and multi-edits" }, + { id: "glob", title: "Glob", description: "Match files using glob patterns" }, + { id: "grep", title: "Grep", description: "Search file contents using regular expressions" }, + { id: "list", title: "List", description: "List files within a directory" }, + { id: "bash", title: "Bash", description: "Run shell commands" }, + { id: "task", title: "Task", description: "Launch sub-agents" }, + { id: "skill", title: "Skill", description: "Load a skill by name" }, + { id: "lsp", title: "LSP", description: "Run language server queries" }, + { id: "todoread", title: "Todo Read", description: "Read the todo list" }, + { id: "todowrite", title: "Todo Write", description: "Update the todo list" }, + { id: "webfetch", title: "Web Fetch", description: "Fetch content from a URL" }, + { id: "websearch", title: "Web Search", description: "Search the web" }, + { id: "codesearch", title: "Code Search", description: "Search code on the web" }, + { id: "external_directory", title: "External Directory", description: "Access files outside the project directory" }, + { id: "doom_loop", title: "Doom Loop", description: "Detect repeated tool calls with identical input" }, +] + +const VALID_ACTIONS = new Set(["allow", "ask", "deny"]) + +function toMap(value: unknown): PermissionMap { + if (value && typeof value === "object" && !Array.isArray(value)) return value as PermissionMap + + const action = getAction(value) + if (action) return { "*": action } + + return {} +} + +function getAction(value: unknown): PermissionAction | undefined { + if (typeof value === "string" && VALID_ACTIONS.has(value as PermissionAction)) return value as PermissionAction + return +} + +function getRuleDefault(value: unknown): PermissionAction | undefined { + const action = getAction(value) + if (action) return action + + if (!value || typeof value !== "object" || Array.isArray(value)) return + + return getAction((value as Record)["*"]) +} export const SettingsPermissions: Component = () => { + const globalSync = useGlobalSync() + + const permission = createMemo(() => { + return toMap(globalSync.data.config.permission) + }) + + const actionFor = (id: string): PermissionAction => { + const value = permission()[id] + const direct = getRuleDefault(value) + if (direct) return direct + + const wildcard = getRuleDefault(permission()["*"]) + if (wildcard) return wildcard + + return "allow" + } + + const setPermission = async (id: string, action: PermissionAction) => { + const before = globalSync.data.config.permission + const map = toMap(before) + const existing = map[id] + + const nextValue = + existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing, "*": action } : action + + globalSync.set("config", "permission", { ...map, [id]: nextValue }) + globalSync.updateConfig({ permission: { [id]: nextValue } }).catch((err: unknown) => { + globalSync.set("config", "permission", before) + const message = err instanceof Error ? err.message : String(err) + showToast({ title: "Failed to update permissions", description: message }) + }) + } + return ( -
-
-

Permissions

-

Permission settings will be configurable here.

+
+
+
+

Permissions

+

Control what tools the server can use by default.

+
+
+ +
+
+

Appearance

+
+ + {(item) => ( + +