From 13f7163365bbc782fc34d77eaaa086afe4795a98 Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Mon, 15 Jun 2026 18:38:56 -0700
Subject: [PATCH] Add agent permission mode controls (#5440)
---
.../components/onboarding/AgentStep.test.tsx | 33 ++--
.../src/components/onboarding/AgentStep.tsx | 53 ++++++-
.../components/onboarding/OnboardingFlow.tsx | 2 +
.../use-onboarding-flow-persistence.ts | 13 +-
.../onboarding/use-onboarding-flow.ts | 25 +++
.../components/settings/AgentsPane.test.tsx | 40 ++++-
.../src/components/settings/AgentsPane.tsx | 99 +++++++++++-
.../src/components/settings/agents-search.ts | 25 +++
src/renderer/src/i18n/locales/en.json | 17 ++-
src/renderer/src/i18n/locales/es.json | 17 ++-
src/renderer/src/i18n/locales/ja.json | 17 ++-
src/renderer/src/i18n/locales/ko.json | 17 ++-
src/renderer/src/i18n/locales/zh.json | 17 ++-
src/shared/tui-agent-launch-defaults.ts | 32 +---
src/shared/tui-agent-permissions.test.ts | 51 +++++++
src/shared/tui-agent-permissions.ts | 143 ++++++++++++++++++
16 files changed, 537 insertions(+), 64 deletions(-)
create mode 100644 src/shared/tui-agent-permissions.test.ts
create mode 100644 src/shared/tui-agent-permissions.ts
diff --git a/src/renderer/src/components/onboarding/AgentStep.test.tsx b/src/renderer/src/components/onboarding/AgentStep.test.tsx
index 5bb6bc57d33..654a339a234 100644
--- a/src/renderer/src/components/onboarding/AgentStep.test.tsx
+++ b/src/renderer/src/components/onboarding/AgentStep.test.tsx
@@ -2,16 +2,21 @@ import { renderToStaticMarkup } from 'react-dom/server'
import { describe, expect, it, vi } from 'vitest'
import { AGENT_CATALOG } from '@/lib/agent-catalog'
import { AgentStep } from './AgentStep'
+import { TooltipProvider } from '@/components/ui/tooltip'
describe('AgentStep', () => {
it('shows the collapsed fallback agents summary', () => {
const html = renderToStaticMarkup(
-
+
+
+
)
expect(html).toContain(`Show ${AGENT_CATALOG.length - 1} more agents→`)
@@ -19,12 +24,16 @@ describe('AgentStep', () => {
it('labels the fallback agents summary as hide when expanded', () => {
const html = renderToStaticMarkup(
-
+
+
+
)
expect(html).toContain('Hide agents')
diff --git a/src/renderer/src/components/onboarding/AgentStep.tsx b/src/renderer/src/components/onboarding/AgentStep.tsx
index 4ef3e8cafbd..96894f357dd 100644
--- a/src/renderer/src/components/onboarding/AgentStep.tsx
+++ b/src/renderer/src/components/onboarding/AgentStep.tsx
@@ -1,8 +1,10 @@
import { useState } from 'react'
-import { Check, ExternalLink } from 'lucide-react'
+import { Check, ExternalLink, Info } from 'lucide-react'
import { getAgentCatalog, AgentIcon, type AgentCatalogEntry } from '@/lib/agent-catalog'
import { cn } from '@/lib/utils'
+import { Checkbox } from '@/components/ui/checkbox'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
+import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import type { TuiAgent } from '../../../../shared/types'
import { translate } from '@/i18n/i18n'
@@ -14,9 +16,18 @@ type AgentStepProps = {
onSelect: (agent: TuiAgent, fromCollapsedSection: boolean) => void
detectedSet: Set
isDetecting: boolean
+ yoloPermissions?: boolean
+ onYoloPermissionsChange?: (enabled: boolean) => void
}
-export function AgentStep({ selectedAgent, onSelect, detectedSet, isDetecting }: AgentStepProps) {
+export function AgentStep({
+ selectedAgent,
+ onSelect,
+ detectedSet,
+ isDetecting,
+ yoloPermissions = true,
+ onYoloPermissionsChange
+}: AgentStepProps) {
const agentCatalog = getAgentCatalog()
const detected = agentCatalog.filter((agent) => detectedSet.has(agent.id))
const rest = agentCatalog.filter((agent) => !detectedSet.has(agent.id))
@@ -82,6 +93,44 @@ export function AgentStep({ selectedAgent, onSelect, detectedSet, isDetecting }:
)}
+
)}
{currentStep.id === 'theme' && (
diff --git a/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.ts b/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.ts
index 1c0b226fd14..d671057bab7 100644
--- a/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.ts
+++ b/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.ts
@@ -4,6 +4,7 @@ import { useAppStore } from '@/store'
import { ONBOARDING_FINAL_STEP, ONBOARDING_FLOW_VERSION } from '../../../../shared/constants'
import type { EventProps } from '../../../../shared/telemetry-events'
import type { GlobalSettings, OnboardingState, TuiAgent } from '../../../../shared/types'
+import { applyAgentPermissionMode } from '../../../../shared/tui-agent-permissions'
import type { StepId, StepNumber } from './use-onboarding-flow-types'
export async function persistStep(
@@ -126,6 +127,7 @@ export function useCloseWith({
type PersistCurrentStepDeps = {
currentStepId: StepId
selectedAgent: TuiAgent | null
+ yoloPermissions: boolean
theme: GlobalSettings['theme']
settings: GlobalSettings | null
updateSettings: (updates: Partial) => Promise | void
@@ -141,6 +143,7 @@ export type PersistCurrentStepResult = {
export function usePersistCurrentStep({
currentStepId,
selectedAgent,
+ yoloPermissions,
theme,
settings,
updateSettings,
@@ -155,7 +158,14 @@ export function usePersistCurrentStep({
try {
if (currentStepId === 'agent') {
const defaultTuiAgent = selectedAgentOrBlank(selectedAgent)
- await updateSettings({ defaultTuiAgent })
+ await updateSettings({
+ defaultTuiAgent,
+ ...applyAgentPermissionMode({
+ mode: yoloPermissions ? 'yolo' : 'manual',
+ agentDefaultArgs: settings.agentDefaultArgs,
+ agentDefaultEnv: settings.agentDefaultEnv
+ })
+ })
const choseAgent = defaultTuiAgent !== 'blank'
const wasAlreadyChosen = onboardingChecklist.choseAgent
onOnboardingChange(
@@ -210,6 +220,7 @@ export function usePersistCurrentStep({
settings,
theme,
updateSettings,
+ yoloPermissions,
setError
])
}
diff --git a/src/renderer/src/components/onboarding/use-onboarding-flow.ts b/src/renderer/src/components/onboarding/use-onboarding-flow.ts
index 88d02e23af2..f278187a24b 100644
--- a/src/renderer/src/components/onboarding/use-onboarding-flow.ts
+++ b/src/renderer/src/components/onboarding/use-onboarding-flow.ts
@@ -33,6 +33,7 @@ import { buildOnboardingFolderAgentStartup } from '@/lib/onboarding-folder-agent
import { resolveOnboardingSettingsHydration } from './onboarding-settings-hydration'
import { openProjectDefaultCheckout } from '../sidebar/project-added-default-checkout'
import { translate } from '@/i18n/i18n'
+import { resolveAgentPermissionModeSummary } from '../../../../shared/tui-agent-permissions'
export { STEPS } from './use-onboarding-flow-types'
export type { StepId, StepNumber } from './use-onboarding-flow-types'
@@ -240,6 +241,12 @@ export function useOnboardingFlow(
? settings.defaultTuiAgent
: null
)
+ const [yoloPermissions, setYoloPermissions] = useState(
+ resolveAgentPermissionModeSummary({
+ agentDefaultArgs: settings?.agentDefaultArgs,
+ agentDefaultEnv: settings?.agentDefaultEnv
+ }) !== 'manual'
+ )
// Why: hydrate theme from saved settings instead of hardcoding 'dark' so users
// who already configured a theme see their choice preselected.
const [theme, setTheme] = useState(settings?.theme ?? 'dark')
@@ -263,6 +270,7 @@ export function useOnboardingFlow(
// fallback defaults, unless the user already interacted with that field.
const themeInteractedRef = useRef(false)
const agentInteractedRef = useRef(false)
+ const yoloPermissionsInteractedRef = useRef(false)
const [settingsHydrated, setSettingsHydrated] = useState(settings != null)
const settingsHydration = resolveOnboardingSettingsHydration({
settings,
@@ -281,6 +289,16 @@ export function useOnboardingFlow(
setSelectedAgent(settingsHydration.selectedAgent)
}
}
+ if (settings && !yoloPermissionsInteractedRef.current) {
+ const nextYoloPermissions =
+ resolveAgentPermissionModeSummary({
+ agentDefaultArgs: settings.agentDefaultArgs,
+ agentDefaultEnv: settings.agentDefaultEnv
+ }) !== 'manual'
+ if (nextYoloPermissions !== yoloPermissions) {
+ setYoloPermissions(nextYoloPermissions)
+ }
+ }
// Why: track user interaction so async settings hydration above doesn't
// overwrite a value the user explicitly chose.
@@ -335,6 +353,10 @@ export function useOnboardingFlow(
},
[]
)
+ const setYoloPermissionsInteractive = useCallback((enabled: boolean) => {
+ yoloPermissionsInteractedRef.current = true
+ setYoloPermissions(enabled)
+ }, [])
const detectedSet = useMemo(() => new Set(detectedAgentIds ?? []), [detectedAgentIds])
const currentStep = STEPS[stepIndex]
@@ -573,6 +595,7 @@ export function useOnboardingFlow(
const persistCurrentStep = usePersistCurrentStep({
currentStepId: currentStep.id,
selectedAgent,
+ yoloPermissions,
theme,
settings,
updateSettings,
@@ -1211,6 +1234,8 @@ export function useOnboardingFlow(
currentStep,
selectedAgent,
setSelectedAgent: setSelectedAgentInteractive,
+ yoloPermissions,
+ setYoloPermissions: setYoloPermissionsInteractive,
theme,
setTheme: setThemeInteractive,
cloneUrl,
diff --git a/src/renderer/src/components/settings/AgentsPane.test.tsx b/src/renderer/src/components/settings/AgentsPane.test.tsx
index 4a65bc68e7c..82fd2e87d4c 100644
--- a/src/renderer/src/components/settings/AgentsPane.test.tsx
+++ b/src/renderer/src/components/settings/AgentsPane.test.tsx
@@ -13,6 +13,7 @@ import { getAgentAwakeDescription, getAgentAwakeTitle } from './agent-awake-copy
import { AgentAwakeSetting } from './AgentAwakeSetting'
import {
AgentAvailabilityControl,
+ AgentPermissionsSetting,
AgentGeneratedTabTitlesSetting,
AgentStatusHooksSetting,
AgentsPane,
@@ -21,6 +22,7 @@ import {
createAgentAvailabilityUpdateQueue
} from './AgentsPane'
import { matchesSettingsSearch } from './settings-search'
+import { TooltipProvider } from '../ui/tooltip'
const detectedAgentsMock = vi.hoisted(() => ({
detectedIds: ['claude'] as TuiAgent[] | null,
@@ -64,11 +66,15 @@ function renderPane(
props: Partial> = {}
): string {
return renderToStaticMarkup(
- React.createElement(AgentsPane, {
- settings,
- updateSettings: vi.fn(),
- ...props
- })
+ React.createElement(
+ TooltipProvider,
+ null,
+ React.createElement(AgentsPane, {
+ settings,
+ updateSettings: vi.fn(),
+ ...props
+ })
+ )
)
}
@@ -259,6 +265,30 @@ describe('AgentsPane', () => {
expect(matchesSettingsSearch('hide', getAgentsPaneSearchEntries())).toBe(true)
})
+ it('includes agent permission search metadata', () => {
+ expect(matchesSettingsSearch('permission', getAgentsPaneSearchEntries())).toBe(true)
+ expect(matchesSettingsSearch('yolo', getAgentsPaneSearchEntries())).toBe(true)
+ expect(matchesSettingsSearch('manual', getAgentsPaneSearchEntries())).toBe(true)
+ })
+
+ it('applies the selected agent permission mode from settings without a mixed segment', () => {
+ const onChange = vi.fn()
+ const element = AgentPermissionsSetting({ mode: 'mixed', onChange })
+ const props = element.props.children.props.action.props as {
+ value: 'yolo'
+ onChange: (value: 'yolo' | 'manual' | 'mixed') => void
+ options: { value: string }[]
+ }
+
+ expect(props.value).toBe('yolo')
+ expect(props.options.map((option) => option.value)).toEqual(['yolo', 'manual'])
+ props.onChange('mixed')
+ expect(onChange).not.toHaveBeenCalled()
+
+ props.onChange('manual')
+ expect(onChange).toHaveBeenCalledWith('manual')
+ })
+
it('keeps catalog agent ids, labels, and commands discoverable in settings search', () => {
for (const agent of AGENT_CATALOG) {
expect(matchesSettingsSearch(agent.id, getAgentsPaneSearchEntries())).toBe(true)
diff --git a/src/renderer/src/components/settings/AgentsPane.tsx b/src/renderer/src/components/settings/AgentsPane.tsx
index 08121326730..93be35b36bb 100644
--- a/src/renderer/src/components/settings/AgentsPane.tsx
+++ b/src/renderer/src/components/settings/AgentsPane.tsx
@@ -2,7 +2,7 @@
selection, per-agent controls, and runtime location together so settings
reconciliation stays visible in one file. */
import { useMemo, useState } from 'react'
-import { Check, ChevronDown, ExternalLink, RefreshCw, Terminal } from 'lucide-react'
+import { Check, ChevronDown, ExternalLink, Info, RefreshCw, Terminal } from 'lucide-react'
import type { GlobalSettings, TuiAgent } from '../../../../shared/types'
import { getAgentCatalog, AgentIcon } from '@/lib/agent-catalog'
import { useDetectedAgents } from '@/hooks/useDetectedAgents'
@@ -33,8 +33,14 @@ import {
resolveTuiAgentLaunchArgs,
resolveTuiAgentLaunchEnv
} from '../../../../shared/tui-agent-launch-defaults'
+import {
+ applyAgentPermissionMode,
+ resolveAgentPermissionModeSummary,
+ type AgentPermissionMode
+} from '../../../../shared/tui-agent-permissions'
import { getSettingOwnershipSummary } from './setting-ownership'
import { translate } from '@/i18n/i18n'
+import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'
export { getAgentsPaneSearchEntries } from './agents-search'
@@ -103,6 +109,11 @@ type AgentAvailabilityControlProps = {
onSetEnabled: (enabled: boolean) => void
}
+type AgentPermissionsSettingProps = {
+ mode: AgentPermissionMode
+ onChange: (mode: Exclude) => void
+}
+
export function buildAgentAvailabilitySettingsUpdate(
settings: Pick,
id: TuiAgent,
@@ -177,6 +188,76 @@ export function AgentAvailabilityControl({
)
}
+export function AgentPermissionsSetting({
+ mode,
+ onChange
+}: AgentPermissionsSettingProps): React.JSX.Element {
+ const visibleMode: Exclude = mode === 'manual' ? 'manual' : 'yolo'
+ return (
+
+
+ {translate('auto.components.settings.AgentsPane.agentPermissions', 'Agent Permissions')}
+
+
+
+
+
+ {translate(
+ 'auto.components.settings.AgentsPane.agentPermissionsTooltip',
+ "Doesn't apply to agents where you've overridden launch arguments."
+ )}
+
+
+
+ }
+ description={translate(
+ 'auto.components.settings.AgentsPane.agentPermissionsDescription',
+ 'Choose whether Orca launches agents with fewer permission prompts or with manual checks.'
+ )}
+ action={
+
+ value={visibleMode}
+ onChange={(nextMode) => {
+ if (nextMode !== 'mixed') {
+ onChange(nextMode)
+ }
+ }}
+ ariaLabel={translate(
+ 'auto.components.settings.AgentsPane.agentPermissions',
+ 'Agent Permissions'
+ )}
+ size="sm"
+ options={[
+ {
+ value: 'yolo',
+ label: translate('auto.components.settings.AgentsPane.agentPermissionsYolo', 'Yolo')
+ },
+ {
+ value: 'manual',
+ label: translate(
+ 'auto.components.settings.AgentsPane.agentPermissionsManual',
+ 'Manual'
+ )
+ }
+ ]}
+ />
+ }
+ />
+
+ )
+}
+
function AgentCommandOverrideInput({
defaultCmd,
cmdOverride,
@@ -617,6 +698,10 @@ export function AgentsPane({
const cmdOverrides = settings.agentCmdOverrides ?? {}
const agentDefaultArgs = settings.agentDefaultArgs ?? {}
const agentDefaultEnv = settings.agentDefaultEnv ?? {}
+ const agentPermissionMode = resolveAgentPermissionModeSummary({
+ agentDefaultArgs,
+ agentDefaultEnv
+ })
const disabledAgents = normalizeDisabledTuiAgents(settings.disabledTuiAgents)
const setDefault = (id: TuiAgent | 'blank' | null): void => {
@@ -661,6 +746,16 @@ export function AgentsPane({
})
}
+ const saveAgentPermissionMode = (mode: Exclude): void => {
+ updateSettings(
+ applyAgentPermissionMode({
+ mode,
+ agentDefaultArgs,
+ agentDefaultEnv
+ })
+ )
+ }
+
// Why: null means detection is in flight, not "all agents are installed".
// Showing the full catalog here makes the default-agent picker flash invalid
// options while switching between Windows and WSL detection contexts.
@@ -742,6 +837,8 @@ export function AgentsPane({
+
+
{detectedAgents.length > 0 && (
[
title: getAgentAwakeTitle(),
description: getAgentAwakeDescription(),
keywords: getAgentAwakeSearchKeywords()
+ },
+ {
+ title: translate(
+ 'auto.components.settings.agents.search.agentPermissions',
+ 'Agent Permissions'
+ ),
+ description: translate(
+ 'auto.components.settings.agents.search.agentPermissionsDescription',
+ 'Switch agent permission defaults between Yolo and Manual.'
+ ),
+ keywords: [
+ ...translateSearchKeyword('auto.components.settings.agents.search.permission', 'permission'),
+ ...translateSearchKeyword(
+ 'auto.components.settings.agents.search.permissions',
+ 'permissions'
+ ),
+ ...translateSearchKeyword('auto.components.settings.agents.search.yolo', 'yolo'),
+ ...translateSearchKeyword('auto.components.settings.agents.search.manual', 'manual'),
+ ...translateSearchKeyword('auto.components.settings.agents.search.skip', 'skip'),
+ ...translateSearchKeyword('auto.components.settings.agents.search.checks', 'checks')
+ ]
}
])
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index 1723d20e6ff..b06bd4cb719 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -4280,7 +4280,13 @@
"cfb3f35775": "Arguments",
"6f99bf5dd0": "No default arguments",
"8fbe1f37c1": "Environment",
- "2d133152fa": "No default environment"
+ "2d133152fa": "No default environment",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsInfo": "Agent permissions info",
+ "agentPermissionsTooltip": "Doesn't apply to agents where you've overridden launch arguments.",
+ "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.",
+ "agentPermissionsYolo": "Yolo",
+ "agentPermissionsManual": "Manual"
},
"AppIconSelector": {
"d5a112dc9b": "Next icon",
@@ -6351,7 +6357,9 @@
"5784ae8c43": "rename",
"8a17fd6026": "stable",
"a79d266f71": "session",
- "afbf35be68": "stable session"
+ "afbf35be68": "stable session",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsDescription": "Switch agent permission defaults between Yolo and Manual."
}
},
"appearance": {
@@ -8811,7 +8819,10 @@
"69af7e9c1c": "isn't on your PATH yet. Orca will set it as your default and you can install it any time.",
"1eee1c7bd8": "No agents detected on your PATH. Pick one to install later, or continue with a blank terminal.",
"hideAgents": "Hide agents",
- "showMoreAgents": "Show {{value0}} more agents→"
+ "showMoreAgents": "Show {{value0}} more agents→",
+ "yoloPermissionsLabel": "Yolo / Dangerously skip permissions",
+ "yoloPermissionsInfo": "Agent permission info",
+ "yoloPermissionsTooltip": "Skip permission checks for agents for less interruptions"
},
"FeatureSetupChecklist": {
"77f74946f5": "Agents can message each other, take tasks, and coordinate handoffs.",
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index 564d25c78af..48c434e7e40 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -4280,7 +4280,13 @@
"cfb3f35775": "Arguments",
"6f99bf5dd0": "No default arguments",
"8fbe1f37c1": "Environment",
- "2d133152fa": "No default environment"
+ "2d133152fa": "No default environment",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsInfo": "Agent permissions info",
+ "agentPermissionsTooltip": "Custom agent arguments stay unchanged when switching modes.",
+ "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.",
+ "agentPermissionsYolo": "Yolo",
+ "agentPermissionsManual": "Manual"
},
"AppIconSelector": {
"d5a112dc9b": "Icono siguiente",
@@ -6314,7 +6320,9 @@
"5784ae8c43": "rebautizar",
"8a17fd6026": "estable",
"a79d266f71": "sesión",
- "afbf35be68": "sesión estable"
+ "afbf35be68": "sesión estable",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsDescription": "Switch agent permission defaults between Yolo and Manual."
}
},
"appearance": {
@@ -8811,7 +8819,10 @@
"69af7e9c1c": "aún no está en tu RUTA. Orca lo configurará como predeterminado y podrás instalarlo en cualquier momento.",
"1eee1c7bd8": "No se detectaron agents en su RUTA. Elija uno para instalarlo más tarde o continúe con un terminal en blanco.",
"hideAgents": "Ocultar agents",
- "showMoreAgents": "Mostrar {{value0}} más agents→"
+ "showMoreAgents": "Mostrar {{value0}} más agents→",
+ "yoloPermissionsLabel": "Yolo / Dangerously skip permissions",
+ "yoloPermissionsInfo": "Agent permission info",
+ "yoloPermissionsTooltip": "Skip permission checks for agents for less interruptions"
},
"FeatureSetupChecklist": {
"77f74946f5": "Los Agents pueden enviarse mensajes entre sí, realizar tareas y coordinar traspasos.",
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index 2722bc433ed..5c08ecb8e6e 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -4265,7 +4265,13 @@
"cfb3f35775": "Arguments",
"6f99bf5dd0": "No default arguments",
"8fbe1f37c1": "Environment",
- "2d133152fa": "No default environment"
+ "2d133152fa": "No default environment",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsInfo": "Agent permissions info",
+ "agentPermissionsTooltip": "Custom agent arguments stay unchanged when switching modes.",
+ "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.",
+ "agentPermissionsYolo": "Yolo",
+ "agentPermissionsManual": "Manual"
},
"AppIconSelector": {
"d5a112dc9b": "次へのアイコン",
@@ -6336,7 +6342,9 @@
"5784ae8c43": "名前変更",
"8a17fd6026": "安定",
"a79d266f71": "セッション",
- "afbf35be68": "安定したセッション"
+ "afbf35be68": "安定したセッション",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsDescription": "Switch agent permission defaults between Yolo and Manual."
}
},
"appearance": {
@@ -8811,7 +8819,10 @@
"69af7e9c1c": "はまだ PATH 上にありません。 Orca はこれをデフォルトとして設定し、いつでもインストールできます。",
"1eee1c7bd8": "PATH 上に agents が検出されませんでした。後でインストールするものを選択するか、空の terminal を使用して続行します。",
"hideAgents": "agents を非表示にする",
- "showMoreAgents": "{{value0}} 件の agents をさらに表示→"
+ "showMoreAgents": "{{value0}} 件の agents をさらに表示→",
+ "yoloPermissionsLabel": "Yolo / Dangerously skip permissions",
+ "yoloPermissionsInfo": "Agent permission info",
+ "yoloPermissionsTooltip": "Skip permission checks for agents for less interruptions"
},
"FeatureSetupChecklist": {
"77f74946f5": "Agents は相互にメッセージを送信し、タスクを実行し、引き継ぎを調整できます。",
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index 5f7e9ce59ce..d8c34a2551e 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -4265,7 +4265,13 @@
"cfb3f35775": "Arguments",
"6f99bf5dd0": "No default arguments",
"8fbe1f37c1": "Environment",
- "2d133152fa": "No default environment"
+ "2d133152fa": "No default environment",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsInfo": "Agent permissions info",
+ "agentPermissionsTooltip": "Custom agent arguments stay unchanged when switching modes.",
+ "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.",
+ "agentPermissionsYolo": "Yolo",
+ "agentPermissionsManual": "Manual"
},
"AppIconSelector": {
"d5a112dc9b": "다음 아이콘",
@@ -6299,7 +6305,9 @@
"5784ae8c43": "이름 변경",
"8a17fd6026": "안정적",
"a79d266f71": "세션",
- "afbf35be68": "안정적인 세션"
+ "afbf35be68": "안정적인 세션",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsDescription": "Switch agent permission defaults between Yolo and Manual."
}
},
"appearance": {
@@ -8811,7 +8819,10 @@
"69af7e9c1c": "아직 PATH에 없습니다. Orca는 이를 기본값으로 설정하며 언제든지 설치할 수 있습니다.",
"1eee1c7bd8": "PATH에서 agents가 감지되지 않습니다. 나중에 설치할 항목을 선택하거나 빈 terminal을 계속 사용하세요.",
"hideAgents": "agents 숨기기",
- "showMoreAgents": "{{value0}}개 더 많은 agents 표시→"
+ "showMoreAgents": "{{value0}}개 더 많은 agents 표시→",
+ "yoloPermissionsLabel": "Yolo / Dangerously skip permissions",
+ "yoloPermissionsInfo": "Agent permission info",
+ "yoloPermissionsTooltip": "Skip permission checks for agents for less interruptions"
},
"FeatureSetupChecklist": {
"77f74946f5": "Agents는 서로 메시지를 보내고, 작업을 수행하고, 핸드오프를 조정할 수 있습니다.",
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index bccdbc88fad..8ed920816b0 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -4265,7 +4265,13 @@
"cfb3f35775": "Arguments",
"6f99bf5dd0": "No default arguments",
"8fbe1f37c1": "Environment",
- "2d133152fa": "No default environment"
+ "2d133152fa": "No default environment",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsInfo": "Agent permissions info",
+ "agentPermissionsTooltip": "Custom agent arguments stay unchanged when switching modes.",
+ "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.",
+ "agentPermissionsYolo": "Yolo",
+ "agentPermissionsManual": "Manual"
},
"AppIconSelector": {
"d5a112dc9b": "下一个图标",
@@ -6299,7 +6305,9 @@
"5784ae8c43": "重命名",
"8a17fd6026": "稳定",
"a79d266f71": "会话",
- "afbf35be68": "稳定会话"
+ "afbf35be68": "稳定会话",
+ "agentPermissions": "Agent Permissions",
+ "agentPermissionsDescription": "Switch agent permission defaults between Yolo and Manual."
}
},
"appearance": {
@@ -8811,7 +8819,10 @@
"69af7e9c1c": "尚未加入 PATH。Orca 会将其设为默认,你可随时安装。",
"1eee1c7bd8": "PATH 中未检测到 agents。可选择一个稍后安装,或使用空白 terminal 继续。",
"hideAgents": "隐藏 Agent",
- "showMoreAgents": "显示另外 {{value0}} 个 Agent→"
+ "showMoreAgents": "显示另外 {{value0}} 个 Agent→",
+ "yoloPermissionsLabel": "Yolo / Dangerously skip permissions",
+ "yoloPermissionsInfo": "Agent permission info",
+ "yoloPermissionsTooltip": "Skip permission checks for agents for less interruptions"
},
"FeatureSetupChecklist": {
"77f74946f5": "Agent 可互相通信、领取任务并协调交接。",
diff --git a/src/shared/tui-agent-launch-defaults.ts b/src/shared/tui-agent-launch-defaults.ts
index deb757143b0..94ca6e103a1 100644
--- a/src/shared/tui-agent-launch-defaults.ts
+++ b/src/shared/tui-agent-launch-defaults.ts
@@ -1,4 +1,5 @@
import { isTuiAgent } from './tui-agent-config'
+import { YOLO_TUI_AGENT_ARGS, YOLO_TUI_AGENT_ENV } from './tui-agent-permissions'
import type { TuiAgent } from './types'
const UNSUPPORTED_TUI_AGENT_ARGS: Partial> = {
@@ -6,35 +7,10 @@ const UNSUPPORTED_TUI_AGENT_ARGS: Partial> =
kilo: ['--dangerously-skip-permissions']
}
-export const DEFAULT_TUI_AGENT_ARGS: Partial> = {
- claude: '--dangerously-skip-permissions',
- 'claude-agent-teams': '--dangerously-skip-permissions',
- openclaude: '--dangerously-skip-permissions',
- codex: '--dangerously-bypass-approvals-and-sandbox',
- gemini: '--yolo',
- antigravity: '--dangerously-skip-permissions',
- aider: '--yes-always',
- amp: '--dangerously-allow-all',
- kiro: '--trust-all-tools',
- crush: '--yolo',
- autohand: '--unrestricted',
- cline: '--auto-approve true',
- 'command-code': '--yolo',
- continue: '--allow "*"',
- cursor: '--yolo',
- kimi: '--yolo',
- 'mistral-vibe': '--agent auto-approve',
- 'qwen-code': '--approval-mode yolo',
- rovo: '--yolo',
- hermes: '--yolo',
- copilot: '--yolo',
- grok: '--permission-mode bypassPermissions',
- devin: '--permission-mode bypass'
-}
+export const DEFAULT_TUI_AGENT_ARGS: Partial> = YOLO_TUI_AGENT_ARGS
-export const DEFAULT_TUI_AGENT_ENV: Partial>> = {
- goose: { GOOSE_MODE: 'auto' }
-}
+export const DEFAULT_TUI_AGENT_ENV: Partial>> =
+ YOLO_TUI_AGENT_ENV
function argPattern(arg: string): RegExp {
return new RegExp(`(^|\\s)${arg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?=\\s|$)`, 'g')
diff --git a/src/shared/tui-agent-permissions.test.ts b/src/shared/tui-agent-permissions.test.ts
new file mode 100644
index 00000000000..7ed1ff440c3
--- /dev/null
+++ b/src/shared/tui-agent-permissions.test.ts
@@ -0,0 +1,51 @@
+import { describe, expect, it } from 'vitest'
+import {
+ applyAgentPermissionMode,
+ resolveAgentPermissionModeSummary,
+ YOLO_TUI_AGENT_ARGS,
+ YOLO_TUI_AGENT_ENV
+} from './tui-agent-permissions'
+
+describe('tui agent permissions', () => {
+ it('recognizes the current default profile as yolo', () => {
+ expect(
+ resolveAgentPermissionModeSummary({
+ agentDefaultArgs: YOLO_TUI_AGENT_ARGS,
+ agentDefaultEnv: YOLO_TUI_AGENT_ENV
+ })
+ ).toBe('yolo')
+ })
+
+ it('recognizes an empty profile as manual', () => {
+ expect(resolveAgentPermissionModeSummary({ agentDefaultArgs: {}, agentDefaultEnv: {} })).toBe(
+ 'manual'
+ )
+ })
+
+ it('preserves custom agent arguments when applying manual mode', () => {
+ const result = applyAgentPermissionMode({
+ mode: 'manual',
+ agentDefaultArgs: {
+ claude: '--dangerously-skip-permissions',
+ codex: '--model gpt-5'
+ },
+ agentDefaultEnv: YOLO_TUI_AGENT_ENV
+ })
+
+ expect(result.agentDefaultArgs.claude).toBe('')
+ expect(result.agentDefaultArgs.codex).toBe('--model gpt-5')
+ expect(result.agentDefaultEnv.goose).toEqual({})
+ })
+
+ it('reports mixed when custom arguments are present', () => {
+ expect(
+ resolveAgentPermissionModeSummary({
+ agentDefaultArgs: {
+ ...YOLO_TUI_AGENT_ARGS,
+ codex: '--model gpt-5'
+ },
+ agentDefaultEnv: YOLO_TUI_AGENT_ENV
+ })
+ ).toBe('mixed')
+ })
+})
diff --git a/src/shared/tui-agent-permissions.ts b/src/shared/tui-agent-permissions.ts
new file mode 100644
index 00000000000..e5716e962a8
--- /dev/null
+++ b/src/shared/tui-agent-permissions.ts
@@ -0,0 +1,143 @@
+import { TUI_AGENT_CONFIG } from './tui-agent-config'
+import type { TuiAgent } from './types'
+
+export type AgentPermissionMode = 'yolo' | 'manual' | 'mixed'
+
+export const YOLO_TUI_AGENT_ARGS: Partial> = {
+ claude: '--dangerously-skip-permissions',
+ 'claude-agent-teams': '--dangerously-skip-permissions',
+ openclaude: '--dangerously-skip-permissions',
+ codex: '--dangerously-bypass-approvals-and-sandbox',
+ gemini: '--yolo',
+ antigravity: '--dangerously-skip-permissions',
+ aider: '--yes-always',
+ amp: '--dangerously-allow-all',
+ kiro: '--trust-all-tools',
+ crush: '--yolo',
+ autohand: '--unrestricted',
+ cline: '--auto-approve true',
+ 'command-code': '--yolo',
+ continue: '--allow "*"',
+ cursor: '--yolo',
+ kimi: '--yolo',
+ 'mistral-vibe': '--agent auto-approve',
+ 'qwen-code': '--approval-mode yolo',
+ rovo: '--yolo',
+ hermes: '--yolo',
+ copilot: '--yolo',
+ grok: '--permission-mode bypassPermissions',
+ devin: '--permission-mode bypass'
+}
+
+export const YOLO_TUI_AGENT_ENV: Partial>> = {
+ goose: { GOOSE_MODE: 'auto' }
+}
+
+const PERMISSION_AGENT_IDS = Object.keys(TUI_AGENT_CONFIG).filter(
+ (agent): agent is TuiAgent => agent in YOLO_TUI_AGENT_ARGS || agent in YOLO_TUI_AGENT_ENV
+)
+
+function normalizeArgs(value: string | null | undefined): string {
+ return value?.trim() ?? ''
+}
+
+function sameEnv(
+ left: Record | null | undefined,
+ right: Record | null | undefined
+): boolean {
+ const leftEntries = Object.entries(left ?? {})
+ const rightEntries = Object.entries(right ?? {})
+ if (leftEntries.length !== rightEntries.length) {
+ return false
+ }
+ return leftEntries.every(([name, value]) => right?.[name] === value)
+}
+
+function resolveAgentPermissionMode(args: string, yoloArgs: string): AgentPermissionMode {
+ if (!args) {
+ return 'manual'
+ }
+ return args === yoloArgs ? 'yolo' : 'mixed'
+}
+
+function resolveAgentEnvPermissionMode(
+ env: Record | null | undefined,
+ yoloEnv: Record | undefined
+): AgentPermissionMode {
+ if (sameEnv(env, {})) {
+ return 'manual'
+ }
+ return sameEnv(env, yoloEnv) ? 'yolo' : 'mixed'
+}
+
+export function resolveAgentPermissionModeSummary(args: {
+ agentDefaultArgs?: Partial> | null
+ agentDefaultEnv?: Partial>> | null
+}): AgentPermissionMode {
+ let sawYolo = false
+ let sawManual = false
+ let sawMixed = false
+
+ for (const agent of PERMISSION_AGENT_IDS) {
+ const modes: AgentPermissionMode[] = []
+ if (agent in YOLO_TUI_AGENT_ARGS) {
+ modes.push(
+ resolveAgentPermissionMode(
+ normalizeArgs(args.agentDefaultArgs?.[agent]),
+ YOLO_TUI_AGENT_ARGS[agent] ?? ''
+ )
+ )
+ }
+ if (agent in YOLO_TUI_AGENT_ENV) {
+ modes.push(
+ resolveAgentEnvPermissionMode(args.agentDefaultEnv?.[agent], YOLO_TUI_AGENT_ENV[agent])
+ )
+ }
+ for (const mode of modes) {
+ if (mode === 'yolo') {
+ sawYolo = true
+ } else if (mode === 'manual') {
+ sawManual = true
+ } else {
+ sawMixed = true
+ }
+ }
+ }
+
+ if (sawMixed || (sawYolo && sawManual)) {
+ return 'mixed'
+ }
+ return sawYolo ? 'yolo' : 'manual'
+}
+
+export function applyAgentPermissionMode(args: {
+ mode: Exclude
+ agentDefaultArgs?: Partial> | null
+ agentDefaultEnv?: Partial>> | null
+}): {
+ agentDefaultArgs: Partial>
+ agentDefaultEnv: Partial>>
+} {
+ const nextArgs = { ...args.agentDefaultArgs }
+ const nextEnv = { ...args.agentDefaultEnv }
+
+ for (const agent of PERMISSION_AGENT_IDS) {
+ if (agent in YOLO_TUI_AGENT_ARGS) {
+ const yoloArgs = YOLO_TUI_AGENT_ARGS[agent] ?? ''
+ const currentArgs = normalizeArgs(nextArgs[agent])
+ if (!currentArgs || currentArgs === yoloArgs) {
+ nextArgs[agent] = args.mode === 'yolo' ? yoloArgs : ''
+ }
+ }
+
+ if (agent in YOLO_TUI_AGENT_ENV) {
+ const yoloEnv = YOLO_TUI_AGENT_ENV[agent]
+ const currentEnv = nextEnv[agent]
+ if (sameEnv(currentEnv, {}) || sameEnv(currentEnv, yoloEnv)) {
+ nextEnv[agent] = args.mode === 'yolo' ? { ...yoloEnv } : {}
+ }
+ }
+ }
+
+ return { agentDefaultArgs: nextArgs, agentDefaultEnv: nextEnv }
+}