Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"exports": {
".": "./src/index.ts",
"./desktop-config": "./src/desktop-config.ts",
"./desktop-menu": "./src/desktop-menu.ts",
"./updater": "./src/updater.ts",
"./wsl/types": "./src/wsl/types.ts",
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/context/permission.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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 { usePlatform } from "./platform"
import { useParams } from "@solidjs/router"
import { decode64 } from "@/utils/base64"
import {
Expand Down Expand Up @@ -49,8 +50,12 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
gate: false,
init: () => {
const params = useParams()
const platform = usePlatform()
const serverSDK = useServerSDK()
const serverSync = useServerSync()
const autoApprove = createMemo(
() => platform.platform === "desktop" && platform.desktopConfig?.()?.permissions?.autoApprove === true,
)

const permissionsEnabled = createMemo(() => {
const directory = decode64(params.dir)
Expand Down Expand Up @@ -142,15 +147,18 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
}

function isAutoAccepting(sessionID: string, directory?: string) {
if (autoApprove()) return true
const session = directory ? serverSync().child(directory, { bootstrap: false })[0].session : []
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
}

function isAutoAcceptingDirectory(directory: string) {
if (autoApprove()) return true
return isDirectoryAutoAccepting(store.autoAccept, directory)
}

function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
if (autoApprove()) return true
const session = directory ? serverSync().child(directory, { bootstrap: false })[0].session : []
return autoRespondsPermission(store.autoAccept, session, permission, directory)
}
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/context/platform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { DesktopMenuAction } from "../desktop-menu"
import { ServerConnection } from "./server"
import type { WslServersPlatform } from "../wsl/types"
import type { UpdaterPlatform } from "../updater"
import type { DesktopConfig } from "../desktop-config"

type PickerPaths = string | string[] | null
type OpenDirectoryPickerOptions = { title?: string; multiple?: boolean }
Expand Down Expand Up @@ -111,6 +112,9 @@ type PlatformBase = {

/** Record a fatal renderer error in platform logs (desktop only) */
recordFatalRendererError?(error: FatalRendererErrorLog): Promise<void>

/** Desktop-only machine config loaded from ~/.config/opencode/desktop.json */
desktopConfig?: Accessor<DesktopConfig | undefined>
}

export type Platform = PlatformBase &
Expand Down
113 changes: 92 additions & 21 deletions packages/app/src/context/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createStore, reconcile } from "solid-js/store"
import { createEffect, createMemo } from "solid-js"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { persisted } from "@/utils/persist"
import { usePlatform } from "./platform"

export interface NotificationSettings {
agent: boolean
Expand Down Expand Up @@ -148,19 +149,42 @@ function withFallback<T>(read: () => T | undefined, fallback: T) {
return createMemo(() => read() ?? fallback)
}

function desktopFallback<T>(read: () => T | undefined, config: () => T | undefined, fallback: T) {
return createMemo(() => config() ?? read() ?? fallback)
}

export const { use: useSettings, provider: SettingsProvider } = createSimpleContext({
name: "Settings",
gate: false,
init: () => {
const platform = usePlatform()
const desktopConfig = () => (platform.platform === "desktop" ? platform.desktopConfig?.() : undefined)
const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
const showFileTree = withFallback(() => store.general?.showFileTree, defaultSettings.general.showFileTree)
const showSearch = withFallback(() => store.general?.showSearch, defaultSettings.general.showSearch)
const showStatus = withFallback(() => store.general?.showStatus, defaultSettings.general.showStatus)
const showCustomAgents = withFallback(
const showFileTree = desktopFallback(
() => store.general?.showFileTree,
() => desktopConfig()?.general?.showFileTree,
defaultSettings.general.showFileTree,
)
const showSearch = desktopFallback(
() => store.general?.showSearch,
() => desktopConfig()?.general?.showSearch,
defaultSettings.general.showSearch,
)
const showStatus = desktopFallback(
() => store.general?.showStatus,
() => desktopConfig()?.general?.showStatus,
defaultSettings.general.showStatus,
)
const showCustomAgents = desktopFallback(
() => store.general?.showCustomAgents,
() => desktopConfig()?.general?.showCustomAgents,
defaultSettings.general.showCustomAgents,
)
const newLayoutDesigns = withFallback(() => store.general?.newLayoutDesigns, newLayoutDesignsDefault)
const newLayoutDesigns = desktopFallback(
() => store.general?.newLayoutDesigns,
() => desktopConfig()?.general?.newLayoutDesigns,
newLayoutDesignsDefault,
)
const visible = (preference: () => boolean) => createMemo(() => !newLayoutDesigns() || preference())

createEffect(() => {
Expand All @@ -181,16 +205,26 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
return store
},
general: {
autoSave: withFallback(() => store.general?.autoSave, defaultSettings.general.autoSave),
autoSave: desktopFallback(
() => store.general?.autoSave,
() => desktopConfig()?.general?.autoSave,
defaultSettings.general.autoSave,
),
setAutoSave(value: boolean) {
setStore("general", "autoSave", value)
},
releaseNotes: withFallback(() => store.general?.releaseNotes, defaultSettings.general.releaseNotes),
releaseNotes: desktopFallback(
() => store.general?.releaseNotes,
() => desktopConfig()?.general?.releaseNotes,
defaultSettings.general.releaseNotes,
),
setReleaseNotes(value: boolean) {
setStore("general", "releaseNotes", value)
},
followup: withFallback(
followup: desktopFallback(
() => (store.general?.followup === "queue" ? "steer" : store.general?.followup),
() =>
desktopConfig()?.general?.followup === "queue" ? "steer" : desktopConfig()?.general?.followup,
defaultSettings.general.followup,
),
setFollowup(value: "queue" | "steer") {
Expand All @@ -200,7 +234,11 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setShowFileTree(value: boolean) {
setStore("general", "showFileTree", value)
},
showNavigation: withFallback(() => store.general?.showNavigation, defaultSettings.general.showNavigation),
showNavigation: desktopFallback(
() => store.general?.showNavigation,
() => desktopConfig()?.general?.showNavigation,
defaultSettings.general.showNavigation,
),
setShowNavigation(value: boolean) {
setStore("general", "showNavigation", value)
},
Expand All @@ -212,33 +250,41 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setShowStatus(value: boolean) {
setStore("general", "showStatus", value)
},
showTerminal: withFallback(() => store.general?.showTerminal, defaultSettings.general.showTerminal),
showTerminal: desktopFallback(
() => store.general?.showTerminal,
() => desktopConfig()?.general?.showTerminal,
defaultSettings.general.showTerminal,
),
setShowTerminal(value: boolean) {
setStore("general", "showTerminal", value)
},
showReasoningSummaries: withFallback(
showReasoningSummaries: desktopFallback(
() => store.general?.showReasoningSummaries,
() => desktopConfig()?.general?.showReasoningSummaries,
defaultSettings.general.showReasoningSummaries,
),
setShowReasoningSummaries(value: boolean) {
setStore("general", "showReasoningSummaries", value)
},
shellToolPartsExpanded: withFallback(
shellToolPartsExpanded: desktopFallback(
() => store.general?.shellToolPartsExpanded,
() => desktopConfig()?.general?.shellToolPartsExpanded,
defaultSettings.general.shellToolPartsExpanded,
),
setShellToolPartsExpanded(value: boolean) {
setStore("general", "shellToolPartsExpanded", value)
},
editToolPartsExpanded: withFallback(
editToolPartsExpanded: desktopFallback(
() => store.general?.editToolPartsExpanded,
() => desktopConfig()?.general?.editToolPartsExpanded,
defaultSettings.general.editToolPartsExpanded,
),
setEditToolPartsExpanded(value: boolean) {
setStore("general", "editToolPartsExpanded", value)
},
showSessionProgressBar: withFallback(
showSessionProgressBar: desktopFallback(
() => store.general?.showSessionProgressBar,
() => desktopConfig()?.general?.showSessionProgressBar,
defaultSettings.general.showSessionProgressBar,
),
setShowSessionProgressBar(value: boolean) {
Expand Down Expand Up @@ -295,7 +341,11 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
},
},
permissions: {
autoApprove: withFallback(() => store.permissions?.autoApprove, defaultSettings.permissions.autoApprove),
autoApprove: desktopFallback(
() => store.permissions?.autoApprove,
() => desktopConfig()?.permissions?.autoApprove,
defaultSettings.permissions.autoApprove,
),
setAutoApprove(value: boolean) {
setStore("permissions", "autoApprove", value)
},
Expand All @@ -315,30 +365,51 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
},
},
sounds: {
agentEnabled: withFallback(() => store.sounds?.agentEnabled, defaultSettings.sounds.agentEnabled),
agentEnabled: desktopFallback(
() => store.sounds?.agentEnabled,
() => desktopConfig()?.sounds?.agentEnabled,
defaultSettings.sounds.agentEnabled,
),
setAgentEnabled(value: boolean) {
setStore("sounds", "agentEnabled", value)
},
agent: withFallback(() => store.sounds?.agent, defaultSettings.sounds.agent),
agent: desktopFallback(
() => store.sounds?.agent,
() => desktopConfig()?.sounds?.agent,
defaultSettings.sounds.agent,
),
setAgent(value: string) {
setStore("sounds", "agent", value)
},
permissionsEnabled: withFallback(
permissionsEnabled: desktopFallback(
() => store.sounds?.permissionsEnabled,
() => desktopConfig()?.sounds?.permissionsEnabled,
defaultSettings.sounds.permissionsEnabled,
),
setPermissionsEnabled(value: boolean) {
setStore("sounds", "permissionsEnabled", value)
},
permissions: withFallback(() => store.sounds?.permissions, defaultSettings.sounds.permissions),
permissions: desktopFallback(
() => store.sounds?.permissions,
() => desktopConfig()?.sounds?.permissions,
defaultSettings.sounds.permissions,
),
setPermissions(value: string) {
setStore("sounds", "permissions", value)
},
errorsEnabled: withFallback(() => store.sounds?.errorsEnabled, defaultSettings.sounds.errorsEnabled),
errorsEnabled: desktopFallback(
() => store.sounds?.errorsEnabled,
() => desktopConfig()?.sounds?.errorsEnabled,
defaultSettings.sounds.errorsEnabled,
),
setErrorsEnabled(value: boolean) {
setStore("sounds", "errorsEnabled", value)
},
errors: withFallback(() => store.sounds?.errors, defaultSettings.sounds.errors),
errors: desktopFallback(
() => store.sounds?.errors,
() => desktopConfig()?.sounds?.errors,
defaultSettings.sounds.errors,
),
setErrors(value: string) {
setStore("sounds", "errors", value)
},
Expand Down
Loading
Loading