feat(agent): add advanced configuration settings

This commit is contained in:
Vaayne 2025-09-23 10:09:11 +08:00
parent 60c85b651f
commit 9c679ede20
13 changed files with 357 additions and 4 deletions

View File

@ -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<Props> = ({ 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<Props> = ({ 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<Props> = ({ agent, trigger, isOpen: _isOpen, o
form.instructions,
form.accessible_paths,
form.allowed_tools,
form.configuration,
agent,
onClose,
t,

View File

@ -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": {

View File

@ -80,6 +80,25 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "设定代理自动执行的请求/回复轮次数。",
"helper": "数值越高可自主运行越久;数值越低更易控制。",
"label": "会话轮次数上限"
},
"permissionMode": {
"description": "控制代理在需要授权时的处理方式。",
"label": "权限模式",
"options": {
"acceptEdits": "自动接受编辑",
"bypassPermissions": "跳过权限检查",
"default": "默认(继续前询问)",
"plan": "规划模式(需审批计划)"
},
"placeholder": "选择权限模式"
},
"title": "高级设置"
},
"essential": "基础设置",
"mcps": "MCP 服务器",
"prompt": "提示词设置"

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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<PermissionMode, string> = {
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<PermissionMode, string> = {
default: 'Default (ask before continuing)',
acceptEdits: 'Accept edits automatically',
bypassPermissions: 'Bypass permission checks',
plan: 'Planning mode (requires plan approval)'
}
type AgentConfigurationState = AgentConfiguration & Record<string, unknown>
interface AgentAdvanceSettingsProps {
agent: GetAgentResponse | undefined | null
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
}
const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState
export const AgentAdvanceSettings: React.FC<AgentAdvanceSettingsProps> = ({ agent, updateAgent }) => {
const { t } = useTranslation()
const [configuration, setConfiguration] = useState<AgentConfigurationState>(defaultConfiguration)
const [maxTurnsInput, setMaxTurnsInput] = useState<string>(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 (
<SettingsContainer>
<SettingsItem>
<SettingsTitle
actions={
<Tooltip content={t('agent.settings.advance.permissionMode.description')} placement="right">
<Info size={16} className="text-foreground-400" />
</Tooltip>
}>
{t('agent.settings.advance.permissionMode.label')}
</SettingsTitle>
<Select
aria-label={t('agent.settings.advance.permissionMode.label')}
selectionMode="single"
selectedKeys={[configuration.permission_mode]}
onSelectionChange={handlePermissionChange}
className="max-w-md"
placeholder={t('agent.settings.advance.permissionMode.placeholder')}>
{permissionOptions.map((option) => (
<SelectItem key={option.key} textValue={option.label}>
{option.label}
</SelectItem>
))}
</Select>
</SettingsItem>
<SettingsItem divider={false}>
<SettingsTitle
actions={
<Tooltip content={t('agent.settings.advance.maxTurns.description')} placement="right">
<Info size={16} className="text-foreground-400" />
</Tooltip>
}>
{t('agent.settings.advance.maxTurns.label')}
</SettingsTitle>
<div className="flex max-w-md flex-col gap-2">
<Input
type="number"
min={1}
value={maxTurnsInput}
onValueChange={setMaxTurnsInput}
onBlur={commitMaxTurns}
onKeyDown={(event) => {
if (event.key === 'Enter') {
commitMaxTurns()
}
}}
aria-label={t('agent.settings.advance.maxTurns.label')}
/>
<span className="text-foreground-500 text-xs">
{t('agent.settings.advance.maxTurns.helper')}
</span>
</div>
</SettingsItem>
</SettingsContainer>
)
}
export default AgentAdvanceSettings

View File

@ -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<AgentSettingPopupParams> = ({ tab, agentId, resolve }) => {
const [open, setOpen] = useState(true)
@ -56,6 +57,10 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ 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<AgentSettingPopupParams> = ({ tab, ag
{menu === 'essential' && <AgentEssentialSettings agent={agent} update={updateAgent} />}
{menu === 'prompt' && <AgentPromptSettings agent={agent} update={updateAgent} />}
{menu === 'mcps' && <AgentMCPSettings agent={agent} updateAgent={updateAgent} />}
{menu === 'advance' && <AgentAdvanceSettings agent={agent} updateAgent={updateAgent} />}
</Settings>
</div>
)

View File

@ -169,6 +169,7 @@ export type BaseAgentForm = {
accessible_paths: string[]
allowed_tools: string[]
mcps?: string[]
configuration?: AgentConfiguration
}
export type AddAgentForm = Omit<BaseAgentForm, 'id'> & { id?: never }