diff --git a/src/renderer/src/components/Popups/agent/AgentModal.tsx b/src/renderer/src/components/Popups/agent/AgentModal.tsx index df70742312..ebce1b05d2 100644 --- a/src/renderer/src/components/Popups/agent/AgentModal.tsx +++ b/src/renderer/src/components/Popups/agent/AgentModal.tsx @@ -25,6 +25,7 @@ import { useApiModels } from '@renderer/hooks/agents/useModels' import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' import { AddAgentForm, + AgentConfigurationSchema, AgentEntity, AgentType, BaseAgentForm, @@ -58,7 +59,8 @@ const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({ model: existing?.model ?? 'claude-4-sonnet', accessible_paths: existing?.accessible_paths ? [...existing.accessible_paths] : [], allowed_tools: existing?.allowed_tools ? [...existing.allowed_tools] : [], - mcps: existing?.mcps ? [...existing.mcps] : [] + mcps: existing?.mcps ? [...existing.mcps] : [], + configuration: AgentConfigurationSchema.parse(existing?.configuration ?? {}) }) interface BaseProps { @@ -321,7 +323,8 @@ export const AgentModal: React.FC = ({ agent, trigger, isOpen: _isOpen, o instructions: form.instructions, model: form.model, accessible_paths: [...form.accessible_paths], - allowed_tools: [...form.allowed_tools] + allowed_tools: [...form.allowed_tools], + configuration: form.configuration ? { ...form.configuration } : undefined } satisfies UpdateAgentForm updateAgent(updatePayload) @@ -334,7 +337,8 @@ export const AgentModal: React.FC = ({ agent, trigger, isOpen: _isOpen, o instructions: form.instructions, model: form.model, accessible_paths: [...form.accessible_paths], - allowed_tools: [...form.allowed_tools] + allowed_tools: [...form.allowed_tools], + configuration: form.configuration ? { ...form.configuration } : undefined } satisfies AddAgentForm addAgent(newAgent) logger.debug('Added agent', newAgent) @@ -353,6 +357,7 @@ export const AgentModal: React.FC = ({ agent, trigger, isOpen: _isOpen, o form.instructions, form.accessible_paths, form.allowed_tools, + form.configuration, agent, onClose, t, diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index b28d3bc351..f78d8abd3c 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "Define how many request/response cycles the agent may complete automatically.", + "helper": "Higher values enable longer autonomous runs; lower values keep sessions short.", + "label": "Conversation turn limit" + }, + "permissionMode": { + "description": "Control how the agent handles actions that require approval.", + "label": "Permission mode", + "options": { + "acceptEdits": "Accept edits automatically", + "bypassPermissions": "Bypass permission checks", + "default": "Default (ask before continuing)", + "plan": "Planning mode (requires plan approval)" + }, + "placeholder": "Choose a permission behavior" + }, + "title": "Advanced Settings" + }, "essential": "Essential Settings", + "mcps": "MCP Servers", "prompt": "Prompt Settings" }, "type": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 75897d6d46..76781468e1 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -80,6 +80,25 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "设定代理自动执行的请求/回复轮次数。", + "helper": "数值越高可自主运行越久;数值越低更易控制。", + "label": "会话轮次数上限" + }, + "permissionMode": { + "description": "控制代理在需要授权时的处理方式。", + "label": "权限模式", + "options": { + "acceptEdits": "自动接受编辑", + "bypassPermissions": "跳过权限检查", + "default": "默认(继续前询问)", + "plan": "规划模式(需审批计划)" + }, + "placeholder": "选择权限模式" + }, + "title": "高级设置" + }, "essential": "基础设置", "mcps": "MCP 服务器", "prompt": "提示词设置" diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 269ef98b39..bbcef66ad7 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "設定代理自動執行的請求/回覆輪次数。", + "helper": "數值越高可自動運行越久;數值越低更容易掌控。", + "label": "會話輪次上限" + }, + "permissionMode": { + "description": "控制代理在需要授權時的處理方式。", + "label": "權限模式", + "options": { + "acceptEdits": "自動接受編輯", + "bypassPermissions": "略過權限檢查", + "default": "預設(繼續前先詢問)", + "plan": "規劃模式(需核准計畫)" + }, + "placeholder": "選擇權限模式" + }, + "title": "進階設定" + }, "essential": "必要設定", + "mcps": "MCP 伺服器", "prompt": "提示設定" }, "type": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 5fd6d5a575..51e2e7df2c 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", + "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", + "label": "[to be translated]:会话轮次数上限" + }, + "permissionMode": { + "description": "[to be translated]:控制代理在需要授权时的处理方式。", + "label": "[to be translated]:权限模式", + "options": { + "acceptEdits": "[to be translated]:自动接受编辑", + "bypassPermissions": "[to be translated]:跳过权限检查", + "default": "[to be translated]:默认(继续前询问)", + "plan": "[to be translated]:规划模式(需审批计划)" + }, + "placeholder": "[to be translated]:选择权限模式" + }, + "title": "[to be translated]:高级设置" + }, "essential": "Βασικές Ρυθμίσεις", + "mcps": "[to be translated]:MCP 服务器", "prompt": "Ρυθμίσεις Προτροπής" }, "type": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 416921fbbb..91c125a5e6 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", + "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", + "label": "[to be translated]:会话轮次数上限" + }, + "permissionMode": { + "description": "[to be translated]:控制代理在需要授权时的处理方式。", + "label": "[to be translated]:权限模式", + "options": { + "acceptEdits": "[to be translated]:自动接受编辑", + "bypassPermissions": "[to be translated]:跳过权限检查", + "default": "[to be translated]:默认(继续前询问)", + "plan": "[to be translated]:规划模式(需审批计划)" + }, + "placeholder": "[to be translated]:选择权限模式" + }, + "title": "[to be translated]:高级设置" + }, "essential": "Configuraciones esenciales", + "mcps": "[to be translated]:MCP 服务器", "prompt": "Configuración de indicaciones" }, "type": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 38502d5f13..119344967d 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", + "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", + "label": "[to be translated]:会话轮次数上限" + }, + "permissionMode": { + "description": "[to be translated]:控制代理在需要授权时的处理方式。", + "label": "[to be translated]:权限模式", + "options": { + "acceptEdits": "[to be translated]:自动接受编辑", + "bypassPermissions": "[to be translated]:跳过权限检查", + "default": "[to be translated]:默认(继续前询问)", + "plan": "[to be translated]:规划模式(需审批计划)" + }, + "placeholder": "[to be translated]:选择权限模式" + }, + "title": "[to be translated]:高级设置" + }, "essential": "Paramètres essentiels", + "mcps": "[to be translated]:MCP 服务器", "prompt": "Paramètres de l'invite" }, "type": { diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 1c8743b218..4322ff6cc2 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", + "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", + "label": "[to be translated]:会话轮次数上限" + }, + "permissionMode": { + "description": "[to be translated]:控制代理在需要授权时的处理方式。", + "label": "[to be translated]:权限模式", + "options": { + "acceptEdits": "[to be translated]:自动接受编辑", + "bypassPermissions": "[to be translated]:跳过权限检查", + "default": "[to be translated]:默认(继续前询问)", + "plan": "[to be translated]:规划模式(需审批计划)" + }, + "placeholder": "[to be translated]:选择权限模式" + }, + "title": "[to be translated]:高级设置" + }, "essential": "必須設定", + "mcps": "[to be translated]:MCP 服务器", "prompt": "プロンプト設定" }, "type": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 3106c19c8e..55670d7578 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", + "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", + "label": "[to be translated]:会话轮次数上限" + }, + "permissionMode": { + "description": "[to be translated]:控制代理在需要授权时的处理方式。", + "label": "[to be translated]:权限模式", + "options": { + "acceptEdits": "[to be translated]:自动接受编辑", + "bypassPermissions": "[to be translated]:跳过权限检查", + "default": "[to be translated]:默认(继续前询问)", + "plan": "[to be translated]:规划模式(需审批计划)" + }, + "placeholder": "[to be translated]:选择权限模式" + }, + "title": "[to be translated]:高级设置" + }, "essential": "Configurações Essenciais", + "mcps": "[to be translated]:MCP 服务器", "prompt": "Configurações de Prompt" }, "type": { diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 78a9ffab50..9604315c62 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -80,7 +80,27 @@ } }, "settings": { + "advance": { + "maxTurns": { + "description": "[to be translated]:设定代理自动执行的请求/回复轮次数。", + "helper": "[to be translated]:数值越高可自主运行越久;数值越低更易控制。", + "label": "[to be translated]:会话轮次数上限" + }, + "permissionMode": { + "description": "[to be translated]:控制代理在需要授权时的处理方式。", + "label": "[to be translated]:权限模式", + "options": { + "acceptEdits": "[to be translated]:自动接受编辑", + "bypassPermissions": "[to be translated]:跳过权限检查", + "default": "[to be translated]:默认(继续前询问)", + "plan": "[to be translated]:规划模式(需审批计划)" + }, + "placeholder": "[to be translated]:选择权限模式" + }, + "title": "[to be translated]:高级设置" + }, "essential": "Основные настройки", + "mcps": "[to be translated]:MCP 服务器", "prompt": "Настройки подсказки" }, "type": { diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentAdvanceSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AgentAdvanceSettings.tsx new file mode 100644 index 0000000000..0e0c4a5583 --- /dev/null +++ b/src/renderer/src/pages/settings/AgentSettings/AgentAdvanceSettings.tsx @@ -0,0 +1,162 @@ +import { Input, Select, SelectItem, Tooltip } from '@heroui/react' +import type { Selection } from '@react-types/shared' +import { + AgentConfiguration, + AgentConfigurationSchema, + GetAgentResponse, + PermissionMode, + PermissionModeSchema, + UpdateAgentForm +} from '@renderer/types' +import { Info } from 'lucide-react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { SettingsContainer, SettingsItem, SettingsTitle } from './shared' + +const permissionModeKeyMap: Record = { + default: 'agent.settings.advance.permissionMode.options.default', + acceptEdits: 'agent.settings.advance.permissionMode.options.acceptEdits', + bypassPermissions: 'agent.settings.advance.permissionMode.options.bypassPermissions', + plan: 'agent.settings.advance.permissionMode.options.plan' +} + +const permissionModeFallback: Record = { + default: 'Default (ask before continuing)', + acceptEdits: 'Accept edits automatically', + bypassPermissions: 'Bypass permission checks', + plan: 'Planning mode (requires plan approval)' +} + +type AgentConfigurationState = AgentConfiguration & Record + +interface AgentAdvanceSettingsProps { + agent: GetAgentResponse | undefined | null + updateAgent: (form: UpdateAgentForm) => Promise | void +} + +const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState + +export const AgentAdvanceSettings: React.FC = ({ agent, updateAgent }) => { + const { t } = useTranslation() + const [configuration, setConfiguration] = useState(defaultConfiguration) + const [maxTurnsInput, setMaxTurnsInput] = useState(String(defaultConfiguration.max_turns)) + + useEffect(() => { + if (!agent) { + setConfiguration(defaultConfiguration) + setMaxTurnsInput(String(defaultConfiguration.max_turns)) + return + } + const parsed = AgentConfigurationSchema.parse(agent.configuration ?? {}) as AgentConfigurationState + setConfiguration(parsed) + setMaxTurnsInput(String(parsed.max_turns)) + }, [agent]) + + const permissionOptions = useMemo( + () => + PermissionModeSchema.options.map((mode) => ({ + key: mode, + label: t(permissionModeKeyMap[mode], permissionModeFallback[mode]) + })) satisfies { key: PermissionMode; label: string }[], + [t] + ) + + const handlePermissionChange = useCallback( + (keys: Selection) => { + if (!agent || keys === 'all') return + const [first] = Array.from(keys) + if (!first) return + const nextMode = first as PermissionMode + setConfiguration((prev) => { + if (prev.permission_mode === nextMode) { + return prev + } + const next = { ...prev, permission_mode: nextMode } as AgentConfigurationState + updateAgent({ id: agent.id, configuration: next } satisfies UpdateAgentForm) + return next + }) + }, + [agent, updateAgent] + ) + + const commitMaxTurns = useCallback(() => { + if (!agent) return + const parsedValue = Number.parseInt(maxTurnsInput, 10) + if (!Number.isFinite(parsedValue)) { + setMaxTurnsInput(String(configuration.max_turns)) + return + } + const sanitized = Math.max(1, parsedValue) + if (sanitized === configuration.max_turns) { + setMaxTurnsInput(String(configuration.max_turns)) + return + } + const next = { ...configuration, max_turns: sanitized } as AgentConfigurationState + setConfiguration(next) + setMaxTurnsInput(String(sanitized)) + updateAgent({ id: agent.id, configuration: next } satisfies UpdateAgentForm) + }, [agent, configuration, maxTurnsInput, updateAgent]) + + if (!agent) { + return null + } + + return ( + + + + + + }> + {t('agent.settings.advance.permissionMode.label')} + + + + + + + + }> + {t('agent.settings.advance.maxTurns.label')} + +
+ { + if (event.key === 'Enter') { + commitMaxTurns() + } + }} + aria-label={t('agent.settings.advance.maxTurns.label')} + /> + + {t('agent.settings.advance.maxTurns.helper')} + +
+
+
+ ) +} + +export default AgentAdvanceSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/index.tsx b/src/renderer/src/pages/settings/AgentSettings/index.tsx index da6cb5355c..a117573825 100644 --- a/src/renderer/src/pages/settings/AgentSettings/index.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/index.tsx @@ -7,6 +7,7 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +import AgentAdvanceSettings from './AgentAdvanceSettings' import AgentEssentialSettings from './AgentEssentialSettings' import AgentMCPSettings from './AgentMCPSettings' import AgentPromptSettings from './AgentPromptSettings' @@ -21,7 +22,7 @@ interface AgentSettingPopupParams extends AgentSettingPopupShowParams { resolve: () => void } -type AgentSettingPopupTab = 'essential' | 'prompt' | 'mcps' | 'session-mcps' +type AgentSettingPopupTab = 'essential' | 'prompt' | 'mcps' | 'advance' | 'session-mcps' const AgentSettingPopupContainer: React.FC = ({ tab, agentId, resolve }) => { const [open, setOpen] = useState(true) @@ -56,6 +57,10 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag { key: 'mcps', label: t('agent.settings.mcps', 'MCP Servers') + }, + { + key: 'advance', + label: t('agent.settings.advance.title', 'Advanced Settings') } ] as const satisfies { key: AgentSettingPopupTab; label: string }[] ).filter(Boolean) @@ -87,6 +92,7 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag {menu === 'essential' && } {menu === 'prompt' && } {menu === 'mcps' && } + {menu === 'advance' && } ) diff --git a/src/renderer/src/types/agent.ts b/src/renderer/src/types/agent.ts index 9ba470b20d..9a7172fb23 100644 --- a/src/renderer/src/types/agent.ts +++ b/src/renderer/src/types/agent.ts @@ -169,6 +169,7 @@ export type BaseAgentForm = { accessible_paths: string[] allowed_tools: string[] mcps?: string[] + configuration?: AgentConfiguration } export type AddAgentForm = Omit & { id?: never }