From 2fef77416ffa015b37c8f06a668fa6cee4b92331 Mon Sep 17 00:00:00 2001 From: Gagancreates Date: Tue, 2 Jun 2026 01:23:18 +0530 Subject: [PATCH] feat(code-mode): approval-policy selector in Settings Surface the approval policy (Ask every time / Auto-approve reads / YOLO) in Settings -> Code Mode, instead of being config-file only. The broker already reads CodeModeConfig.approvalPolicy; this plumbs it through the codeMode:getConfig / setConfig IPC + main handlers and adds the picker UI (with a one-line explanation of each level). Defaults to "ask". --- apps/x/apps/main/src/ipc.ts | 4 +- .../src/components/settings-dialog.tsx | 55 ++++++++++++++++++- apps/x/packages/shared/src/ipc.ts | 4 +- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index 0b2111a5..e5d407f8 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -643,11 +643,11 @@ export function setupIpcHandlers() { 'codeMode:getConfig': async () => { const repo = container.resolve('codeModeConfigRepo'); const config = await repo.getConfig(); - return { enabled: config.enabled }; + return { enabled: config.enabled, approvalPolicy: config.approvalPolicy }; }, 'codeMode:setConfig': async (_event, args) => { const repo = container.resolve('codeModeConfigRepo'); - await repo.setConfig({ enabled: args.enabled }); + await repo.setConfig({ enabled: args.enabled, approvalPolicy: args.approvalPolicy }); invalidateCopilotInstructionsCache(); return { success: true }; }, diff --git a/apps/x/apps/renderer/src/components/settings-dialog.tsx b/apps/x/apps/renderer/src/components/settings-dialog.tsx index 3a88937f..15d9cc27 100644 --- a/apps/x/apps/renderer/src/components/settings-dialog.tsx +++ b/apps/x/apps/renderer/src/components/settings-dialog.tsx @@ -25,6 +25,7 @@ import { useTheme } from "@/contexts/theme-context" import { toast } from "sonner" import { AccountSettings } from "@/components/settings/account-settings" import { ConnectedAccountsSettings } from "@/components/settings/connected-accounts-settings" +import type { ApprovalPolicy } from "@x/shared/src/code-mode.js" type ConfigTab = "account" | "connections" | "models" | "mcp" | "security" | "code-mode" | "appearance" | "note-tagging" | "help" @@ -1712,6 +1713,7 @@ function AgentStatusRow({ function CodeModeSettings({ dialogOpen }: { dialogOpen: boolean }) { const [enabled, setEnabled] = useState(false) + const [approvalPolicy, setApprovalPolicy] = useState('ask') const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [status, setStatus] = useState(null) @@ -1736,7 +1738,10 @@ function CodeModeSettings({ dialogOpen }: { dialogOpen: boolean }) { setLoading(true) try { const result = await window.ipc.invoke("codeMode:getConfig", null) - if (!cancelled) setEnabled(result.enabled) + if (!cancelled) { + setEnabled(result.enabled) + setApprovalPolicy(result.approvalPolicy ?? 'ask') + } } catch { if (!cancelled) setEnabled(false) } finally { @@ -1752,7 +1757,7 @@ function CodeModeSettings({ dialogOpen }: { dialogOpen: boolean }) { setSaving(true) setEnabled(next) try { - await window.ipc.invoke("codeMode:setConfig", { enabled: next }) + await window.ipc.invoke("codeMode:setConfig", { enabled: next, approvalPolicy }) window.dispatchEvent(new Event("code-mode-config-changed")) toast.success(next ? "Code mode enabled" : "Code mode disabled") } catch { @@ -1761,7 +1766,22 @@ function CodeModeSettings({ dialogOpen }: { dialogOpen: boolean }) { } finally { setSaving(false) } - }, []) + }, [approvalPolicy]) + + const handlePolicyChange = useCallback(async (next: ApprovalPolicy) => { + const prev = approvalPolicy + setSaving(true) + setApprovalPolicy(next) + try { + await window.ipc.invoke("codeMode:setConfig", { enabled, approvalPolicy: next }) + window.dispatchEvent(new Event("code-mode-config-changed")) + } catch { + setApprovalPolicy(prev) + toast.error("Failed to update approval policy") + } finally { + setSaving(false) + } + }, [enabled, approvalPolicy]) const anyReady = status?.claude.installed && status?.claude.signedIn || status?.codex.installed && status?.codex.signedIn @@ -1832,6 +1852,35 @@ function CodeModeSettings({ dialogOpen }: { dialogOpen: boolean }) { /> + {enabled && ( +
+
Approvals
+
+ How the coding agent checks in before changing files or running commands. You always see + everything it does in the timeline — this only controls the prompts. +
+ +
+ {approvalPolicy === 'ask' && 'You approve every file change and command the agent wants to run.'} + {approvalPolicy === 'auto-approve-reads' && 'Reading and searching run automatically; you still approve writes, edits, and commands.'} + {approvalPolicy === 'yolo' && 'The agent runs everything — writes, edits, and commands — without asking. Use only in folders you trust.'} +
+
+ )} + {enabled && status && !anyReady && (
diff --git a/apps/x/packages/shared/src/ipc.ts b/apps/x/packages/shared/src/ipc.ts index 572e0af7..f3acffa1 100644 --- a/apps/x/packages/shared/src/ipc.ts +++ b/apps/x/packages/shared/src/ipc.ts @@ -19,7 +19,7 @@ import { ZListToolkitsResponse } from './composio.js'; import { BrowserStateSchema } from './browser-control.js'; import { BillingInfoSchema } from './billing.js'; import { EmailBlockSchema, GmailThreadSchema } from './blocks.js'; -import { PermissionDecision } from './code-mode.js'; +import { PermissionDecision, ApprovalPolicy } from './code-mode.js'; // ============================================================================ // Runtime Validation Schemas (Single Source of Truth) @@ -431,11 +431,13 @@ const ipcSchemas = { req: z.null(), res: z.object({ enabled: z.boolean(), + approvalPolicy: ApprovalPolicy.optional(), }), }, 'codeMode:setConfig': { req: z.object({ enabled: z.boolean(), + approvalPolicy: ApprovalPolicy.optional(), }), res: z.object({ success: z.literal(true),