Merge branch 'feat/agents-new' of https://github.com/CherryHQ/cherry-studio into feat/agents-new

This commit is contained in:
suyao 2025-09-23 13:25:27 +08:00
commit 7cdc80c3e2
No known key found for this signature in database
25 changed files with 1697 additions and 218 deletions

View File

@ -21,13 +21,18 @@ This file provides guidance to AI coding assistants when working with code in th
## Development Commands
- **Install**: `yarn install`
- **Development**: `yarn dev` - Runs Electron app in development mode
- **Debug**: `yarn debug` - Starts with debugging enabled, use chrome://inspect
- **Build Check**: `yarn build:check` - REQUIRED before commits (lint + test + typecheck)
- **Test**: `yarn test` - Run all tests (Vitest)
- **Single Test**: `yarn test:main` or `yarn test:renderer`
- **Lint**: `yarn lint` - Fix linting issues and run typecheck
- **Install**: `yarn install` - Install all project dependencies
- **Development**: `yarn dev` - Runs Electron app in development mode with hot reload
- **Debug**: `yarn debug` - Starts with debugging enabled, use `chrome://inspect` to attach debugger
- **Build Check**: `yarn build:check` - **REQUIRED** before commits (lint + test + typecheck)
- If having i18n sort issues, run `yarn i18n:sync` first to sync template
- If having formatting issues, run `yarn format` first
- **Test**: `yarn test` - Run all tests (Vitest) across main and renderer processes
- **Single Test**:
- `yarn test:main` - Run tests for main process only
- `yarn test:renderer` - Run tests for renderer process only
- **Lint**: `yarn lint` - Fix linting issues and run TypeScript type checking
- **Format**: `yarn format` - Auto-format code using Biome
## Project Architecture

View File

@ -69,7 +69,7 @@
"test:e2e": "yarn playwright test",
"test:lint": "oxlint --deny-warnings && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --cache",
"test:scripts": "vitest scripts",
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && yarn typecheck && yarn check:i18n",
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && yarn typecheck && yarn check:i18n && yarn format:check",
"format": "biome format --write && biome lint --write",
"format:check": "biome format && biome lint",
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",

View File

@ -113,14 +113,14 @@ class MCPApiService extends EventEmitter {
const client = await mcpService.initClient(server)
const tools = await client.listTools()
logger.info(`Server with id ${id} info:`, { tools: JSON.stringify(tools) })
logger.silly(`Server with id ${id} info:`, { tools: JSON.stringify(tools.tools) })
return {
id: server.id,
name: server.name,
type: server.type,
description: server.description,
tools
tools: tools.tools
}
} catch (error: any) {
logger.error(`Failed to get server info with id ${id}:`, error)

View File

@ -5,15 +5,22 @@ import React from 'react'
export interface ModelLabelProps extends Omit<React.ComponentPropsWithRef<'div'>, 'children'> {
model?: ApiModel
classNames?: {
container?: string
avatar?: string
modelName?: string
divider?: string
providerName?: string
}
}
export const ApiModelLabel: React.FC<ModelLabelProps> = ({ model, className, ...props }) => {
export const ApiModelLabel: React.FC<ModelLabelProps> = ({ model, className, classNames, ...props }) => {
return (
<div className={cn('flex items-center gap-1', className)} {...props}>
<Avatar src={model ? getModelLogo(model.id) : undefined} className="h-4 w-4" />
<span>
{model?.name} | {model?.provider_name}
</span>
<div className={cn('flex items-center gap-1', className, classNames?.container)} {...props}>
<Avatar src={model ? getModelLogo(model.id) : undefined} className={cn('h-4 w-4', classNames?.avatar)} />
<span className={classNames?.modelName}>{model?.name}</span>
<span className={classNames?.divider}> | </span>
<span className={classNames?.providerName}>{model?.provider_name}</span>
</div>
)
}

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,
@ -57,7 +58,9 @@ const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({
instructions: existing?.instructions,
model: existing?.model ?? 'claude-4-sonnet',
accessible_paths: existing?.accessible_paths ? [...existing.accessible_paths] : [],
allowed_tools: existing?.allowed_tools ? [...existing.allowed_tools] : []
allowed_tools: existing?.allowed_tools ? [...existing.allowed_tools] : [],
mcps: existing?.mcps ? [...existing.mcps] : [],
configuration: AgentConfigurationSchema.parse(existing?.configuration ?? {})
})
interface BaseProps {
@ -320,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)
@ -333,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)
@ -352,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

@ -1,6 +1,5 @@
import {
Button,
Chip,
cn,
Form,
Input,
@ -18,6 +17,7 @@ import {
} from '@heroui/react'
import { loggerService } from '@logger'
import type { Selection } from '@react-types/shared'
import { AllowedToolsSelect } from '@renderer/components/agent'
import { getModelLogo } from '@renderer/config/models'
import { useAgent } from '@renderer/hooks/agents/useAgent'
import { useApiModels } from '@renderer/hooks/agents/useModels'
@ -58,7 +58,8 @@ const buildSessionForm = (existing?: SessionWithTools, agent?: AgentWithTools):
? [...existing.allowed_tools]
: agent?.allowed_tools
? [...agent.allowed_tools]
: []
: [],
mcps: existing?.mcps ? [...existing.mcps] : agent?.mcps ? [...agent.mcps] : []
})
interface BaseProps {
@ -197,21 +198,6 @@ export const SessionModal: React.FC<Props> = ({
[availableTools]
)
const renderSelectedTools = useCallback((items: SelectedItems<Tool>) => {
if (!items.length) {
return null
}
return (
<div className="flex flex-wrap gap-2">
{items.map((item) => (
<Chip key={item.key} size="sm" variant="flat" className="max-w-[160px] truncate">
{item.data?.name ?? item.textValue ?? item.key}
</Chip>
))}
</div>
)
}, [])
const modelOptions = useMemo(() => {
// mocked data. not final version
return (models ?? []).map((model) => ({
@ -266,7 +252,8 @@ export const SessionModal: React.FC<Props> = ({
instructions: form.instructions,
model: form.model,
accessible_paths: [...form.accessible_paths],
allowed_tools: [...(form.allowed_tools ?? [])]
allowed_tools: [...(form.allowed_tools ?? [])],
mcps: [...(form.mcps ?? [])]
} satisfies UpdateSessionForm
updateSession(updatePayload)
@ -278,7 +265,8 @@ export const SessionModal: React.FC<Props> = ({
instructions: form.instructions,
model: form.model,
accessible_paths: [...form.accessible_paths],
allowed_tools: [...(form.allowed_tools ?? [])]
allowed_tools: [...(form.allowed_tools ?? [])],
mcps: [...(form.mcps ?? [])]
} satisfies CreateSessionForm
const createdSession = await createSession(newSession)
if (createdSession) {
@ -300,6 +288,7 @@ export const SessionModal: React.FC<Props> = ({
form.instructions,
form.accessible_paths,
form.allowed_tools,
form.mcps,
session,
onClose,
onSessionCreated,
@ -359,31 +348,11 @@ export const SessionModal: React.FC<Props> = ({
value={form.description ?? ''}
onValueChange={onDescChange}
/>
<Select
selectionMode="multiple"
<AllowedToolsSelect
items={availableTools}
selectedKeys={selectedToolKeys}
onSelectionChange={onAllowedToolsChange}
label={t('agent.session.allowed_tools.label')}
placeholder={t('agent.session.allowed_tools.placeholder')}
description={
availableTools.length
? t('agent.session.allowed_tools.helper')
: t('agent.session.allowed_tools.empty')
}
isDisabled={!availableTools.length}
items={availableTools}
renderValue={renderSelectedTools}>
{(tool) => (
<SelectItem key={tool.id} textValue={tool.name}>
<div className="flex flex-col">
<span className="font-medium text-sm">{tool.name}</span>
{tool.description ? (
<span className="text-foreground-500 text-xs">{tool.description}</span>
) : null}
</div>
</SelectItem>
)}
</Select>
/>
<Textarea label={t('common.prompt')} value={form.instructions ?? ''} onValueChange={onInstChange} />
</ModalBody>
<ModalFooter className="w-full">

View File

@ -0,0 +1,54 @@
import { Chip, cn, Select, SelectedItems, SelectItem, SelectProps } from '@heroui/react'
import { Tool } from '@renderer/types'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
export interface AllowedToolsSelectProps extends Omit<SelectProps, 'children'> {
items: Tool[]
}
export const AllowedToolsSelect: React.FC<AllowedToolsSelectProps> = (props) => {
const { t } = useTranslation()
const { items: availableTools, className, ...rest } = props
const renderSelectedTools = useCallback((items: SelectedItems<Tool>) => {
if (!items.length) {
return null
}
return (
<div className="flex flex-wrap gap-2">
{items.map((item) => (
<Chip key={item.key} size="sm" variant="flat" className="max-w-[160px] truncate">
{item.data?.name ?? item.textValue ?? item.key}
</Chip>
))}
</div>
)
}, [])
return (
<Select
aria-label={t('agent.session.allowed_tools.label')}
selectionMode="multiple"
isMultiline
label={t('agent.session.allowed_tools.label')}
placeholder={t('agent.session.allowed_tools.placeholder')}
description={
availableTools.length ? t('agent.session.allowed_tools.helper') : t('agent.session.allowed_tools.empty')
}
isDisabled={!availableTools.length}
items={availableTools}
renderValue={renderSelectedTools}
className={cn('max-w-xl', className)}
{...rest}>
{(tool) => (
<SelectItem key={tool.id} textValue={tool.name}>
<div className="flex flex-col">
<span className="font-medium text-sm">{tool.name}</span>
{tool.description ? <span className="text-foreground-500 text-xs">{tool.description}</span> : null}
</div>
</SelectItem>
)}
</Select>
)
}

View File

@ -0,0 +1 @@
export { AllowedToolsSelect } from './AllowedToolsSelect'

View File

@ -1,10 +1,13 @@
import { ApiModelsFilter } from '@renderer/types'
import { useApiModels } from './useModels'
export type UseModelProps = {
id: string
id?: string
filter?: ApiModelsFilter
}
export const useApiModel = (id?: string) => {
const { models } = useApiModels()
export const useApiModel = ({ id, filter }: UseModelProps) => {
const { models } = useApiModels(filter)
return models.find((model) => model.id === id)
}

View File

@ -46,9 +46,9 @@
},
"allowed_tools": {
"empty": "No tools available for this agent.",
"helper": "Choose which tools are pre-approved. Unselected tools require approval before use.",
"label": "Allowed tools",
"placeholder": "Select allowed tools"
"helper": "Pre-approved tools run without manual approval. Unselected tools require approval before use.",
"label": "Pre-approved tools",
"placeholder": "Select pre-approved tools"
},
"create": {
"error": {
@ -80,8 +80,106 @@
}
},
"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",
"prompt": "Prompt Settings"
"prompt": "Prompt Settings",
"tooling": {
"mcp": {
"description": "Connect MCP servers to unlock additional tools you can approve above.",
"empty": "No MCP servers detected. Add one from the MCP settings page.",
"manageHint": "Need advanced configuration? Visit Settings → MCP Servers.",
"toggle": "Toggle {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "Pre-approves trusted filesystem tools so edits run immediately.",
"description": "File edits and filesystem operations are automatically approved.",
"title": "Auto-accept file edits"
},
"bypassPermissions": {
"behavior": "Every tool is pre-approved automatically.",
"description": "All permission prompts are skipped — use with caution.",
"title": "Bypass permission checks",
"warning": "Use with caution — all tools will run without asking for approval."
},
"confirmChange": {
"description": "Switching modes updates the automatically approved tools.",
"title": "Change permission mode?"
},
"default": {
"behavior": "Read-only tools are pre-approved automatically.",
"description": "Read-only tools are pre-approved; everything else still needs permission.",
"title": "Default (ask before continuing)"
},
"plan": {
"behavior": "Read-only defaults are pre-approved while execution remains disabled.",
"description": "Shares the default read-only tool set but presents a plan before execution.",
"title": "Planning mode (coming soon)"
}
},
"preapproved": {
"autoBadge": "Added by mode",
"autoDescription": "This tool is auto-approved by the current permission mode.",
"empty": "No tools match your filters.",
"mcpBadge": "MCP tool",
"requiresApproval": "Requires approval when disabled",
"search": "Search tools",
"toggle": "Toggle {{name}}",
"warning": {
"description": "Enable only tools you trust. Mode defaults are highlighted automatically.",
"title": "Pre-approved tools run without manual review."
}
},
"review": {
"autoTools": "Auto: {{count}}",
"customTools": "Custom: {{count}}",
"helper": "Changes save automatically. Adjust the steps above any time to fine-tune permissions.",
"mcp": "MCP: {{count}}",
"mode": "Mode: {{mode}}"
},
"steps": {
"mcp": {
"title": "MCP servers"
},
"permissionMode": {
"title": "Step 1 · Permission mode"
},
"preapproved": {
"title": "Step 2 · Pre-approved tools"
},
"review": {
"title": "Step 3 · Review"
}
},
"tab": "Tooling & permissions"
},
"tools": {
"approved": "approved",
"caution": "Pre-approved tools bypass human review. Enable only trusted tools.",
"description": "Choose which tools can run without manual approval.",
"requiresPermission": "Requires permission when not pre-approved.",
"tab": "Pre-approved tools",
"title": "Pre-approved tools",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "Agent Type",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "当前智能体暂无可用工具。",
"helper": "选择预先授权的工具,未选中的工具在使用时需要手动审批。",
"label": "允许的工具",
"placeholder": "选择允许使用的工具"
"label": "预先授权工具",
"placeholder": "选择预先授权的工具"
},
"create": {
"error": {
@ -80,8 +80,106 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "设定代理自动执行的请求/回复轮次数。",
"helper": "数值越高可自主运行越久;数值越低更易控制。",
"label": "会话轮次数上限"
},
"permissionMode": {
"description": "控制代理在需要授权时的处理方式。",
"label": "权限模式",
"options": {
"acceptEdits": "自动接受编辑",
"bypassPermissions": "跳过权限检查",
"default": "默认(继续前询问)",
"plan": "规划模式(需审批计划)"
},
"placeholder": "选择权限模式"
},
"title": "高级设置"
},
"essential": "基础设置",
"prompt": "提示词设置"
"prompt": "提示词设置",
"tooling": {
"mcp": {
"description": "连接 MCP 服务器即可解锁更多可在上方预先授权的工具。",
"empty": "未检测到 MCP 服务器,请前往 MCP 设置页添加。",
"manageHint": "需要更多配置?前往 设置 → MCP 服务器。",
"toggle": "切换 {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "预先授权受信任的文件系统工具,允许即时执行。",
"description": "文件编辑和文件系统操作将自动通过审批。",
"title": "自动接受文件编辑"
},
"bypassPermissions": {
"behavior": "所有工具都会被自动预先授权。",
"description": "所有权限提示都会被跳过,请谨慎使用。",
"title": "跳过所有权限检查",
"warning": "危险:所有工具都会在无审批情况下执行。"
},
"confirmChange": {
"description": "切换模式会更新自动预先授权的工具。",
"title": "确认切换权限模式?"
},
"default": {
"behavior": "只读工具会自动预先授权。",
"description": "只读工具会自动预先授权,其它操作仍需权限。",
"title": "默认(继续前询问)"
},
"plan": {
"behavior": "默认的只读工具会自动预先授权,但执行仍被禁用。",
"description": "继承默认的只读工具集,并会在执行前先呈现计划。",
"title": "规划模式(即将支持)"
}
},
"preapproved": {
"autoBadge": "模式自动添加",
"autoDescription": "该工具由当前权限模式自动预先授权。",
"empty": "没有符合筛选条件的工具。",
"mcpBadge": "MCP 工具",
"requiresApproval": "禁用时需要人工审批",
"search": "搜索工具",
"toggle": "切换 {{name}}",
"warning": {
"description": "仅启用你信任的工具。模式默认值会自动标注。",
"title": "预先授权的工具将在无人工审核时运行。"
}
},
"review": {
"autoTools": "自动:{{count}}",
"customTools": "自定义:{{count}}",
"helper": "设置会自动保存,可随时返回上方步骤进行调整。",
"mcp": "MCP{{count}}",
"mode": "模式:{{mode}}"
},
"steps": {
"mcp": {
"title": "MCP 服务器"
},
"permissionMode": {
"title": "步骤 1 · 权限模式"
},
"preapproved": {
"title": "步骤 2 · 预先授权工具"
},
"review": {
"title": "步骤 3 · 总览"
}
},
"tab": "工具与权限"
},
"tools": {
"approved": "已授权",
"caution": "预先授权的工具会跳过人工审核,请仅启用可信的工具。",
"description": "选择哪些工具可以在无需人工审批的情况下执行。",
"requiresPermission": "未预先授权时需要人工审批。",
"tab": "预先授权工具",
"title": "预先授权工具",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "智能体类型",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "目前此代理沒有可用的工具。",
"helper": "選擇預先授權的工具,未選取的工具在使用時需要手動審批。",
"label": "允許的工具",
"placeholder": "選擇允許使用的工具"
"label": "預先授權工具",
"placeholder": "選擇預先授權的工具"
},
"create": {
"error": {
@ -80,8 +80,106 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "設定代理自動執行的請求/回覆輪次数。",
"helper": "數值越高可自動運行越久;數值越低更容易掌控。",
"label": "會話輪次上限"
},
"permissionMode": {
"description": "控制代理在需要授權時的處理方式。",
"label": "權限模式",
"options": {
"acceptEdits": "自動接受編輯",
"bypassPermissions": "略過權限檢查",
"default": "預設(繼續前先詢問)",
"plan": "規劃模式(需核准計畫)"
},
"placeholder": "選擇權限模式"
},
"title": "進階設定"
},
"essential": "必要設定",
"prompt": "提示設定"
"prompt": "提示設定",
"tooling": {
"mcp": {
"description": "連線 MCP 伺服器即可解鎖更多可在上方預先授權的工具。",
"empty": "尚未偵測到 MCP 伺服器,請前往 MCP 設定頁新增。",
"manageHint": "需要進階設定?前往 設定 → MCP 伺服器。",
"toggle": "切換 {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "預先授權受信任的檔案系統工具,允許即時執行。",
"description": "檔案編輯與檔案系統操作會自動通過核准。",
"title": "自動接受檔案編輯"
},
"bypassPermissions": {
"behavior": "所有工具都會被自動預先授權。",
"description": "所有權限提示都會被略過,請務必謹慎使用。",
"title": "略過所有權限檢查",
"warning": "警告:所有工具都會在無核准情況下執行。"
},
"confirmChange": {
"description": "切換模式會更新自動預先授權的工具。",
"title": "確認切換權限模式?"
},
"default": {
"behavior": "唯讀工具會自動預先授權。",
"description": "唯讀工具會自動預先授權,其它操作仍需核准。",
"title": "預設(繼續前先詢問)"
},
"plan": {
"behavior": "預設的唯讀工具會自動預先授權,但執行仍被停用。",
"description": "沿用預設的唯讀工具集,並會在執行前先呈現計畫。",
"title": "規劃模式(即將支援)"
}
},
"preapproved": {
"autoBadge": "模式自動添加",
"autoDescription": "此工具由目前的權限模式自動預先授權。",
"empty": "沒有符合篩選條件的工具。",
"mcpBadge": "MCP 工具",
"requiresApproval": "停用時需要人工核准",
"search": "搜尋工具",
"toggle": "切換 {{name}}",
"warning": {
"description": "僅啟用你信任的工具。模式預設值會自動標示。",
"title": "預先授權的工具將在無人工審查下執行。"
}
},
"review": {
"autoTools": "自動:{{count}}",
"customTools": "自訂:{{count}}",
"helper": "設定會自動儲存,可隨時回到上方步驟調整。",
"mcp": "MCP{{count}}",
"mode": "模式:{{mode}}"
},
"steps": {
"mcp": {
"title": "MCP 伺服器"
},
"permissionMode": {
"title": "步驟 1 · 權限模式"
},
"preapproved": {
"title": "步驟 2 · 預先授權工具"
},
"review": {
"title": "步驟 3 · 檢視"
}
},
"tab": "工具與權限"
},
"tools": {
"approved": "已授權",
"caution": "預先授權的工具會略過人工審查,請僅啟用可信任的工具。",
"description": "選擇哪些工具可在無需人工核准的情況下執行。",
"requiresPermission": "未預先授權時需要人工核准。",
"tab": "預先授權工具",
"title": "預先授權工具",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "代理類型",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "Δεν υπάρχουν διαθέσιμα εργαλεία για αυτόν τον agent.",
"helper": "Επιλέξτε ποια εργαλεία είναι προεγκεκριμένα. Τα μη επιλεγμένα θα χρειαστούν έγκριση πριν από τη χρήση.",
"label": "Επιτρεπόμενα εργαλεία",
"placeholder": "Επιλέξτε επιτρεπόμενα εργαλεία"
"label": "Προεγκεκριμένα εργαλεία",
"placeholder": "Επιλέξτε προεγκεκριμένα εργαλεία"
},
"create": {
"error": {
@ -80,8 +80,107 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "Ορίστε τον αριθμό γύρων αιτήματος/απάντησης που θα εκτελούνται αυτόματα από τον διαμεσολαβητή.",
"helper": "Όσο υψηλότερη είναι η τιμή, τόσο περισσότερο μπορεί να λειτουργεί αυτόνομα· όσο χαμηλότερη είναι, τόσο πιο εύκολα ελέγχεται.",
"label": "Όριο αριθμού γύρων συνεδρίας"
},
"permissionMode": {
"description": "Ο τρόπος με τον οποίο ο πληρεξούσιος ελεγκτής χειρίζεται την κατάσταση όταν απαιτείται εξουσιοδότηση.",
"label": "Λειτουργία δικαιωμάτων",
"options": {
"acceptEdits": "Αυτόματη αποδοχή επεξεργασίας",
"bypassPermissions": "παράλειψη ελέγχου δικαιωμάτων",
"default": "Προεπιλογή (να ερωτηθεί πριν από τη συνέχεια)",
"plan": "Λειτουργία σχεδιασμού (απαιτείται έγκριση σχεδίου)"
},
"placeholder": "Επιλέξτε λειτουργία δικαιωμάτων"
},
"title": "Ρυθμίσεις για προχωρημένους"
},
"essential": "Βασικές Ρυθμίσεις",
"prompt": "Ρυθμίσεις Προτροπής"
"mcps": "Διακομιστής MCP",
"prompt": "Ρυθμίσεις Προτροπής",
"tooling": {
"mcp": {
"description": "Συνδέστε διακομιστές MCP για να ξεκλειδώσετε πρόσθετα εργαλεία που μπορείτε να εγκρίνετε παραπάνω.",
"empty": "Δεν εντοπίστηκαν διακομιστές MCP. Προσθέστε έναν από τη σελίδα ρυθμίσεων MCP.",
"manageHint": "Χρειάζεστε προηγμένη διαμόρφωση; Επισκεφθείτε Ρυθμίσεις → Διακομιστές MCP.",
"toggle": "Εναλλαγή {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "Προεγκρίνει αξιόπιστα εργαλεία συστήματος αρχείων ώστε οι επεξεργασίες να εκτελούνται αμέσως.",
"description": "Οι επεξεργασίες αρχείων και οι λειτουργίες του συστήματος αρχείων εγκρίνονται αυτόματα.",
"title": "Αυτόματη αποδοχή επεξεργασιών αρχείων"
},
"bypassPermissions": {
"behavior": "Κάθε εργαλείο εγκρίνεται αυτόματα εκ των προτέρων.",
"description": "Όλα τα αιτήματα άδειας παραλείπονται — χρησιμοποιήστε με προσοχή.",
"title": "Παράκαμψη ελέγχων αδειών",
"warning": "Χρησιμοποιήστε με προσοχή — όλα τα εργαλεία θα εκτελεστούν χωρίς να ζητηθεί έγκριση."
},
"confirmChange": {
"description": "Η αλλαγή λειτουργιών ενημερώνει τα εργαλεία που εγκρίνονται αυτόματα.",
"title": "Αλλαγή λειτουργίας δικαιωμάτων;"
},
"default": {
"behavior": "Κανένα εργαλείο δεν εγκρίνεται αυτόματα εκ των προτέρων.",
"description": "Ισχύουν οι κανονικοί έλεγχοι δικαιωμάτων.",
"title": "Προεπιλογή (να ερωτηθώ πριν συνεχίσω)"
},
"plan": {
"behavior": "Μόνο εργαλεία μόνο για ανάγνωση. Η εκτέλεση είναι απενεργοποιημένη.",
"description": "Ο Claude μπορεί να χρησιμοποιεί μόνο εργαλεία μόνο για ανάγνωση και παρουσιάζει ένα σχέδιο πριν από την εκτέλεση.",
"title": "Λειτουργία σχεδιασμού (έρχεται σύντομα)"
}
},
"preapproved": {
"autoBadge": "Προστέθηκε από τη λειτουργία",
"autoDescription": "Αυτό το εργαλείο έχει εγκριθεί αυτόματα από την τρέχουσα λειτουργία δικαιωμάτων.",
"empty": "Δεν υπάρχουν εργαλεία που να ταιριάζουν με τα φίλτρα σας.",
"mcpBadge": "εργαλείο MCP",
"requiresApproval": "Απαιτείται έγκριση όταν είναι απενεργοποιημένο",
"search": "Εργαλεία αναζήτησης",
"toggle": "Εναλλαγή {{name}}",
"warning": {
"description": "Ενεργοποιήστε μόνο εργαλεία που εμπιστεύεστε. Οι προεπιλογές λειτουργίας τονίζονται αυτόματα.",
"title": "Τα προεγκεκριμένα εργαλεία εκτελούνται χωρίς χειροκίνητη αναθεώρηση."
}
},
"review": {
"autoTools": "Αυτόματο: {{count}}",
"customTools": "Προσαρμοσμένο: {{count}}",
"helper": "Οι αλλαγές αποθηκεύονται αυτόματα. Προσαρμόστε τα παραπάνω βήματα ανά πάσα στιγμή για να εξειδικεύσετε τα δικαιώματα.",
"mcp": "MCP: {{count}}",
"mode": "Λειτουργία: {{mode}}"
},
"steps": {
"mcp": {
"title": "Διακομιστές MCP"
},
"permissionMode": {
"title": "Βήμα 1 · Λειτουργία αδειών"
},
"preapproved": {
"title": "Βήμα 2 · Προεγκεκριμένα εργαλεία"
},
"review": {
"title": "Βήμα 3 · Ανασκόπηση"
}
},
"tab": "Εργαλεία & άδειες"
},
"tools": {
"approved": "εγκεκριμένο",
"caution": "Εργαλεία προεγκεκριμένα παρακάμπτουν την ανθρώπινη αξιολόγηση. Ενεργοποιήστε μόνο έμπιστα εργαλεία.",
"description": "Επιλέξτε ποια εργαλεία μπορούν να εκτελούνται χωρίς χειροκίνητη έγκριση.",
"requiresPermission": "Απαιτείται άδεια όταν δεν έχει προεγκριθεί.",
"tab": "Προεγκεκριμένα εργαλεία",
"title": "Προεγκεκριμένα εργαλεία",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "Τύπος Πράκτορα",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "No hay herramientas disponibles para este agente.",
"helper": "Elige qué herramientas quedan preaprobadas. Las no seleccionadas requerirán aprobación manual antes de usarse.",
"label": "Herramientas permitidas",
"placeholder": "Selecciona las herramientas permitidas"
"label": "Herramientas preaprobadas",
"placeholder": "Seleccionar herramientas preaprobadas"
},
"create": {
"error": {
@ -80,8 +80,107 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "Establece el número de rondas de solicitud/respuesta que el agente ejecutará automáticamente.",
"helper": "Cuanto mayor es el valor, más tiempo puede funcionar de forma autónoma; cuanto menor es el valor, más fácil es de controlar.",
"label": "Límite máximo de turnos de conversación"
},
"permissionMode": {
"description": "Cómo el agente de control maneja las situaciones que requieren autorización.",
"label": "modo de permisos",
"options": {
"acceptEdits": "Aceptar ediciones automáticamente",
"bypassPermissions": "Omitir verificación de permisos",
"default": "Predeterminado (preguntar antes de continuar)",
"plan": "Modo de planificación (requiere aprobación del plan)"
},
"placeholder": "Seleccionar modo de permisos"
},
"title": "Configuración avanzada"
},
"essential": "Configuraciones esenciales",
"prompt": "Configuración de indicaciones"
"mcps": "Servidor MCP",
"prompt": "Configuración de indicaciones",
"tooling": {
"mcp": {
"description": "Conecta servidores MCP para desbloquear herramientas adicionales que puedes aprobar arriba.",
"empty": "No se detectaron servidores MCP. Añade uno desde la página de configuración de MCP.",
"manageHint": "¿Necesitas configuración avanzada? Visita Configuración → Servidores MCP.",
"toggle": "Alternar {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "Preaprueba herramientas de sistema de archivos de confianza para que las ediciones se ejecuten inmediatamente.",
"description": "Las ediciones de archivos y las operaciones del sistema de archivos se aprueban automáticamente.",
"title": "Aceptar automáticamente las ediciones de archivos"
},
"bypassPermissions": {
"behavior": "Cada herramienta está pre-aprobada automáticamente.",
"description": "Todos los mensajes de permiso se omiten — úsalo con precaución.",
"title": "Omitir verificaciones de permisos",
"warning": "Usar con precaución: todas las herramientas se ejecutarán sin pedir aprobación."
},
"confirmChange": {
"description": "Cambiar de modo actualiza las herramientas aprobadas automáticamente.",
"title": "¿Cambiar modo de permisos?"
},
"default": {
"behavior": "No se aprueban herramientas automáticamente de antemano.",
"description": "Se aplican los controles de permisos normales.",
"title": "Predeterminado (preguntar antes de continuar)"
},
"plan": {
"behavior": "Solo herramientas de solo lectura. La ejecución está deshabilitada.",
"description": "Claude solo puede usar herramientas de solo lectura y presenta un plan antes de ejecutarlo.",
"title": "[to be translated]:Planning mode (coming soon)"
}
},
"preapproved": {
"autoBadge": "Añadido por modo",
"autoDescription": "Esta herramienta está aprobada automáticamente por el modo de permisos actual.",
"empty": "Ninguna herramienta coincide con tus filtros.",
"mcpBadge": "herramienta MCP",
"requiresApproval": "Requiere aprobación cuando está deshabilitado",
"search": "Herramientas de búsqueda",
"toggle": "Alternar {{name}}",
"warning": {
"description": "Habilita solo las herramientas en las que confíes. Los valores predeterminados del modo se resaltan automáticamente.",
"title": "Herramientas preaprobadas se ejecutan sin revisión manual."
}
},
"review": {
"autoTools": "Auto: {{count}}",
"customTools": "Personalizado: {{count}}",
"helper": "Los cambios se guardan automáticamente. Ajusta los pasos anteriores en cualquier momento para afinar los permisos.",
"mcp": "MCP: {{count}}",
"mode": "Modo: {{mode}}"
},
"steps": {
"mcp": {
"title": "servidores MCP"
},
"permissionMode": {
"title": "Paso 1 · Modo de permiso"
},
"preapproved": {
"title": "Paso 2 · Herramientas preaprobadas"
},
"review": {
"title": "Paso 3 · Revisar"
}
},
"tab": "Herramientas y permisos"
},
"tools": {
"approved": "aprobado",
"caution": "Herramientas preaprobadas omiten la revisión humana. Habilita solo herramientas de confianza.",
"description": "Elige qué herramientas pueden ejecutarse sin aprobación manual.",
"requiresPermission": "Requiere permiso cuando no está preaprobado.",
"tab": "Herramientas preaprobadas",
"title": "Herramientas preaprobadas",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "Tipo de Agente",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "Aucun outil disponible pour cet agent.",
"helper": "Choisissez les outils préapprouvés. Les outils non sélectionnés nécessiteront une approbation avant utilisation.",
"label": "Outils autorisés",
"placeholder": "Sélectionner les outils autorisés"
"label": "Outils pré-approuvés",
"placeholder": "Sélectionner des outils pré-approuvés"
},
"create": {
"error": {
@ -80,8 +80,107 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "Définir le nombre de cycles de requête/réponse exécutés automatiquement par l'agent.",
"helper": "Une valeur plus élevée permet une autonomie prolongée ; une valeur plus faible facilite le contrôle.",
"label": "Limite maximale de tours de conversation"
},
"permissionMode": {
"description": "Contrôle la manière dont l'agent gère les demandes d'autorisation.",
"label": "mode d'autorisation",
"options": {
"acceptEdits": "Accepter automatiquement les modifications",
"bypassPermissions": "Passer la vérification des autorisations",
"default": "Par défaut (demander avant de continuer)",
"plan": "Mode de planification (plan soumis à approbation)"
},
"placeholder": "Choisir le mode d'autorisation"
},
"title": "Paramètres avancés"
},
"essential": "Paramètres essentiels",
"prompt": "Paramètres de l'invite"
"mcps": "Serveur MCP",
"prompt": "Paramètres de l'invite",
"tooling": {
"mcp": {
"description": "Connectez des serveurs MCP pour débloquer des outils supplémentaires que vous pouvez approuver ci-dessus.",
"empty": "Aucun serveur MCP détecté. Ajoutez-en un depuis la page des paramètres MCP.",
"manageHint": "Besoin d'une configuration avancée ? Visitez Paramètres → Serveurs MCP.",
"toggle": "Basculer {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "Pré-approuve les outils de système de fichiers de confiance afin que les modifications s'exécutent immédiatement.",
"description": "Les modifications de fichiers et les opérations sur le système de fichiers sont automatiquement approuvées.",
"title": "Accepter automatiquement les modifications de fichiers"
},
"bypassPermissions": {
"behavior": "Chaque outil est pré-approuvé automatiquement.",
"description": "Toutes les demandes de permission sont ignorées — à utiliser avec prudence.",
"title": "Contourner les vérifications de permission",
"warning": "Utiliser avec prudence — tous les outils s'exécuteront sans demander d'approbation."
},
"confirmChange": {
"description": "Le changement de mode met à jour les outils approuvés automatiquement.",
"title": "Changer le mode d'autorisation ?"
},
"default": {
"behavior": "Aucun outil nest pré-approuvé automatiquement.",
"description": "Les vérifications d'autorisation normales s'appliquent.",
"title": "Par défaut (demander avant de continuer)"
},
"plan": {
"behavior": "Outils en lecture seule uniquement. L'exécution est désactivée.",
"description": "Claude ne peut utiliser que des outils en lecture seule et présente un plan avant l'exécution.",
"title": "Mode de planification (à venir)"
}
},
"preapproved": {
"autoBadge": "Ajouté par mode",
"autoDescription": "Cet outil est automatiquement approuvé par le mode d'autorisation actuel.",
"empty": "Aucun outil ne correspond à vos filtres.",
"mcpBadge": "outil MCP",
"requiresApproval": "Nécessite une approbation lorsquil est désactivé",
"search": "Outils de recherche",
"toggle": "Basculer {{name}}",
"warning": {
"description": "Activez uniquement les outils en lesquels vous avez confiance. Les paramètres par défaut du mode sont mis en surbrillance automatiquement.",
"title": "Les outils pré-approuvés s'exécutent sans révision manuelle."
}
},
"review": {
"autoTools": "Auto : {{count}}",
"customTools": "Personnalisé : {{count}}",
"helper": "Les modifications sont enregistrées automatiquement. Ajustez les étapes ci-dessus à tout moment pour affiner les autorisations.",
"mcp": "MCP : {{count}}",
"mode": "Mode : {{mode}}"
},
"steps": {
"mcp": {
"title": "Serveurs MCP"
},
"permissionMode": {
"title": "Étape 1 · Mode dautorisation"
},
"preapproved": {
"title": "Étape 2 · Outils pré-approuvés"
},
"review": {
"title": "Étape 3 · Révision"
}
},
"tab": "Outils et autorisations"
},
"tools": {
"approved": "approuvé",
"caution": "Outils pré-approuvés contournent la révision humaine. Activez uniquement les outils de confiance.",
"description": "Choisissez quels outils peuvent s'exécuter sans approbation manuelle.",
"requiresPermission": "Nécessite une autorisation lorsqu'elle n'est pas préapprouvée.",
"tab": "Outils pré-approuvés",
"title": "Outils pré-approuvés",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "Type d'agent",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "このエージェントが利用できるツールはありません。",
"helper": "事前承認済みのツールを選択します。未選択のツールは使用時に承認が必要になります。",
"label": "許可されたツール",
"placeholder": "許可するツールを選択"
"label": "事前承認済みツール",
"placeholder": "事前承認するツールを選択"
},
"create": {
"error": {
@ -80,8 +80,107 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "プロキシが自動的に実行するリクエスト/レスポンスのラウンド数を設定します。",
"helper": "数値が高いほど自律動作の時間が長くなり、数値が低いほど制御しやすくなります。",
"label": "会話ラウンド数の上限"
},
"permissionMode": {
"description": "制御エージェントが認可を必要とする場合の処理方法。",
"label": "権限モード",
"options": {
"acceptEdits": "自動的に編集を受け入れる",
"bypassPermissions": "権限チェックをスキップ",
"default": "デフォルト(続行する前に確認)",
"plan": "計画モード(承認が必要な計画)"
},
"placeholder": "権限モードを選択"
},
"title": "高級設定"
},
"essential": "必須設定",
"prompt": "プロンプト設定"
"mcps": "MCPサーバー",
"prompt": "プロンプト設定",
"tooling": {
"mcp": {
"description": "MCPサーバーを接続して、上で承認できる追加ツールを解放します。",
"empty": "MCPサーバーが検出されませんでした。MCP設定ページから追加してください。",
"manageHint": "高度な設定が必要ですか?設定 → MCPサーバーにアクセスしてください。",
"toggle": "{{name}}を切り替え"
},
"permissionMode": {
"acceptEdits": {
"behavior": "信頼できるファイルシステムツールを事前に承認し、編集が即座に実行されるようにします。",
"description": "ファイルの編集とファイルシステムの操作は自動的に承認されます。",
"title": "ファイル編集を自動承認"
},
"bypassPermissions": {
"behavior": "すべてのツールは自動的に事前承認されます。",
"description": "すべての権限プロンプトはスキップされます — 注意して使用してください。",
"title": "権限チェックをバイパス",
"warning": "注意して使用してください — すべてのツールは承認なしで実行されます。"
},
"confirmChange": {
"description": "モードを切り替えると、自動承認されたツールが更新されます。",
"title": "パーミッションモードを変更しますか?"
},
"default": {
"behavior": "ツールは自動的に事前承認されません。",
"description": "通常の権限チェックが適用されます。",
"title": "デフォルト(続行する前に確認)"
},
"plan": {
"behavior": "読み取り専用ツールのみ。実行は無効です。",
"description": "Claudeは読み取り専用ツールのみを使用し、実行前に計画を提示します。",
"title": "計画モード(近日公開)"
}
},
"preapproved": {
"autoBadge": "モードによって追加されました",
"autoDescription": "このツールは現在の権限モードによって自動承認されています。",
"empty": "フィルターに一致するツールはありません。",
"mcpBadge": "MCPツール",
"requiresApproval": "無効にするには承認が必要",
"search": "検索ツール",
"toggle": "{{name}}を切り替える",
"warning": {
"description": "信頼できるツールのみを有効にしてください。モードのデフォルトは自動的に強調表示されます。",
"title": "事前承認されたツールは手動でのレビューなしに実行されます。"
}
},
"review": {
"autoTools": "自動: {{count}}",
"customTools": "カスタム: {{count}}",
"helper": "変更は自動的に保存されます。権限を微調整するには、上記の手順をいつでも調整してください。",
"mcp": "MCP: {{count}}",
"mode": "モード: {{mode}}"
},
"steps": {
"mcp": {
"title": "MCPサーバー"
},
"permissionMode": {
"title": "ステップ1・権限モード"
},
"preapproved": {
"title": "ステップ2・事前承認済みツール"
},
"review": {
"title": "ステップ3・レビュー"
}
},
"tab": "ツールと権限"
},
"tools": {
"approved": "承認済み",
"caution": "事前承認したツールは人によるレビューをスキップします。信頼できるツールのみ有効にしてください。",
"description": "人による承認なしで実行できるツールを選択します。",
"requiresPermission": "事前承認されていない場合は承認が必要です。",
"tab": "事前承認済みツール",
"title": "事前承認済みツール",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "エージェントタイプ",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "Não há ferramentas disponíveis para este agente.",
"helper": "Escolha quais ferramentas ficam pré-autorizadas. As não selecionadas exigirão aprovação antes do uso.",
"label": "Ferramentas permitidas",
"placeholder": "Selecione as ferramentas permitidas"
"label": "Ferramentas pré-aprovadas",
"placeholder": "Selecionar ferramentas pré-aprovadas"
},
"create": {
"error": {
@ -80,8 +80,107 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "Defina o número de ciclos de solicitação/resposta executados automaticamente pelo agente.",
"helper": "Quanto maior o valor, mais tempo pode funcionar de forma autônoma; quanto menor o valor, mais fácil de controlar.",
"label": "Limite máximo de turnos de conversação"
},
"permissionMode": {
"description": "Como o agente de controle lida com situações que exigem autorização.",
"label": "Modo de permissão",
"options": {
"acceptEdits": "Aceitar edições automaticamente",
"bypassPermissions": "忽略检查 de permissão",
"default": "Padrão (perguntar antes de continuar)",
"plan": "Modo de planejamento (plano sujeito a aprovação)"
},
"placeholder": "Selecionar modo de permissão"
},
"title": "Configurações avançadas"
},
"essential": "Configurações Essenciais",
"prompt": "Configurações de Prompt"
"mcps": "Servidor MCP",
"prompt": "Configurações de Prompt",
"tooling": {
"mcp": {
"description": "Conecte servidores MCP para desbloquear ferramentas adicionais que você pode aprovar acima.",
"empty": "Nenhum servidor MCP detectado. Adicione um na página de configurações do MCP.",
"manageHint": "Precisa de configuração avançada? Visite Configurações → Servidores MCP.",
"toggle": "Alternar {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "Pré-aplica ferramentas confiáveis do sistema de arquivos para que as edições sejam executadas imediatamente.",
"description": "As edições de arquivos e operações do sistema de arquivos são aprovadas automaticamente.",
"title": "Aceitar automaticamente edições de arquivos"
},
"bypassPermissions": {
"behavior": "Cada ferramenta é pré-aprovada automaticamente.",
"description": "Todos os pedidos de permissão são ignorados — use com cautela.",
"title": "Ignorar verificações de permissão",
"warning": "Use com cautela — todas as ferramentas serão executadas sem solicitar aprovação."
},
"confirmChange": {
"description": "Alternar modos atualiza as ferramentas aprovadas automaticamente.",
"title": "Alterar modo de permissão?"
},
"default": {
"behavior": "Nenhuma ferramenta é pré-aprovada automaticamente.",
"description": "Verificações normais de permissão aplicam-se.",
"title": "Padrão (perguntar antes de continuar)"
},
"plan": {
"behavior": "Ferramentas somente leitura. A execução está desabilitada.",
"description": "Claude só pode usar ferramentas de leitura e apresenta um plano antes da execução.",
"title": "Modo de planejamento (em breve)"
}
},
"preapproved": {
"autoBadge": "Adicionado por modo",
"autoDescription": "Esta ferramenta é aprovada automaticamente pelo modo de permissão atual.",
"empty": "Nenhuma ferramenta corresponde aos seus filtros.",
"mcpBadge": "Ferramenta MCP",
"requiresApproval": "Requer aprovação quando desativado",
"search": "Ferramentas de pesquisa",
"toggle": "Alternar {{name}}",
"warning": {
"description": "Ative apenas ferramentas em que confie. As configurações padrão do modo são destacadas automaticamente.",
"title": "Ferramentas pré-aprovadas são executadas sem revisão manual."
}
},
"review": {
"autoTools": "Auto: {{count}}",
"customTools": "Personalizado: {{count}}",
"helper": "As alterações são salvas automaticamente. Ajuste as etapas acima a qualquer momento para refinar as permissões.",
"mcp": "MCP: {{count}}",
"mode": "Modo: {{mode}}"
},
"steps": {
"mcp": {
"title": "Servidores MCP"
},
"permissionMode": {
"title": "Passo 1 · Modo de permissão"
},
"preapproved": {
"title": "Passo 2 · Ferramentas pré-aprovadas"
},
"review": {
"title": "Passo 3 · Revisão"
}
},
"tab": "Ferramentas e permissões"
},
"tools": {
"approved": "aprovado",
"caution": "Ferramentas pré-aprovadas ignoram a revisão humana. Ative apenas ferramentas confiáveis.",
"description": "Escolha quais ferramentas podem ser executadas sem aprovação manual.",
"requiresPermission": "Requer permissão quando não pré-aprovado.",
"tab": "Ferramentas pré-aprovadas",
"title": "Ferramentas pré-aprovadas",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "Tipo de Agente",

View File

@ -47,8 +47,8 @@
"allowed_tools": {
"empty": "Для этого агента нет доступных инструментов.",
"helper": "Выберите инструменты с предварительным допуском. Неотмеченные инструменты потребуют подтверждения перед использованием.",
"label": "Разрешённые инструменты",
"placeholder": "Выберите разрешённые инструменты"
"label": "Предварительно одобренные инструменты",
"placeholder": "Выберите предварительно одобренные инструменты"
},
"create": {
"error": {
@ -80,8 +80,107 @@
}
},
"settings": {
"advance": {
"maxTurns": {
"description": "Установить количество циклов запрос/ответ, выполняемых автоматически через прокси.",
"helper": "Чем выше значение, тем дольше может работать автономно; чем ниже значение, тем легче контролировать.",
"label": "Максимальное количество раундов сеанса"
},
"permissionMode": {
"description": "Как агент управления обрабатывает ситуации, требующие авторизации.",
"label": "Режим разрешений",
"options": {
"acceptEdits": "Автоматически принимать правки",
"bypassPermissions": "Пропустить проверку разрешений",
"default": "По умолчанию (спросить перед продолжением)",
"plan": "Режим планирования (требуется утверждение плана)"
},
"placeholder": "Выбрать режим разрешений"
},
"title": "Расширенные настройки"
},
"essential": "Основные настройки",
"prompt": "Настройки подсказки"
"mcps": "MCP сервер",
"prompt": "Настройки подсказки",
"tooling": {
"mcp": {
"description": "Подключите серверы MCP, чтобы разблокировать дополнительные инструменты, которые вы можете одобрить выше.",
"empty": "Серверы MCP не обнаружены. Добавьте один на странице настроек MCP.",
"manageHint": "Нужна расширенная настройка? Перейдите в Настройки → Серверы MCP.",
"toggle": "Переключить {{name}}"
},
"permissionMode": {
"acceptEdits": {
"behavior": "Предварительно одобряет доверенные инструменты файловой системы, чтобы изменения выполнялись немедленно.",
"description": "Редактирование файлов и операции с файловой системой автоматически одобряются.",
"title": "Автоматически принимать изменения файлов"
},
"bypassPermissions": {
"behavior": "Каждый инструмент автоматически предварительно одобрен.",
"description": "Все запросы разрешений пропускаются — используйте с осторожностью.",
"title": "Обход проверки разрешений",
"warning": "Использовать с осторожностью — все инструменты будут запущены без запроса подтверждения."
},
"confirmChange": {
"description": "Переключение режимов обновляет автоматически одобренные инструменты.",
"title": "Изменить режим разрешений?"
},
"default": {
"behavior": "Никакие инструменты не предварительно одобряются автоматически.",
"description": "Применяются обычные проверки разрешений.",
"title": "По умолчанию (спросить перед продолжением)"
},
"plan": {
"behavior": "Только инструменты чтения. Выполнение отключено.",
"description": "Claude может использовать только инструменты только для чтения и представляет план перед выполнением.",
"title": "Режим планирования (скоро появится)"
}
},
"preapproved": {
"autoBadge": "Добавлено режимом",
"autoDescription": "Этот инструмент автоматически одобрен текущим режимом разрешений.",
"empty": "Нет инструментов, соответствующих вашим фильтрам.",
"mcpBadge": "Инструмент MCP",
"requiresApproval": "Требует одобрения при отключении",
"search": "Инструменты поиска",
"toggle": "Переключить {{name}}",
"warning": {
"description": "Включайте только те инструменты, которым доверяете. Настройки по умолчанию выделяются автоматически.",
"title": "Предварительно одобренные инструменты запускаются без ручной проверки."
}
},
"review": {
"autoTools": "Авто: {{count}}",
"customTools": "Пользовательские: {{count}}",
"helper": "Изменения сохраняются автоматически. В любое время можно скорректировать шаги выше, чтобы уточнить разрешения.",
"mcp": "MCP: {{count}}",
"mode": "Режим: {{mode}}"
},
"steps": {
"mcp": {
"title": "Серверы MCP"
},
"permissionMode": {
"title": "Шаг 1 · Режим разрешений"
},
"preapproved": {
"title": "Шаг 2 · Предварительно одобренные инструменты"
},
"review": {
"title": "Шаг 3 · Обзор"
}
},
"tab": "Инструменты и разрешения"
},
"tools": {
"approved": "одобрено",
"caution": "Предварительно одобренные инструменты обходят проверку человеком. Включайте только доверенные инструменты.",
"description": "Выберите, какие инструменты могут запускаться без ручного подтверждения.",
"requiresPermission": "Требуется разрешение, если не предварительно одобрено.",
"tab": "Предварительно одобренные инструменты",
"title": "Предварительно одобренные инструменты",
"toggle": "{{defaultValue}}"
}
},
"type": {
"label": "Тип агента",

View File

@ -41,7 +41,8 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
const { chat } = useRuntime()
const { activeTopicOrSession, activeAgentId } = chat
const { agent } = useAgent(activeAgentId)
const agentModel = useApiModel(agent?.model)
// TODO: filter is temporally for agent since it cannot get all models once
const agentModel = useApiModel({ id: agent?.model, filter: { providerType: 'anthropic' } })
useShortcut('toggle_show_assistants', toggleShowAssistants)
@ -104,7 +105,9 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
{activeTopicOrSession === 'topic' && <SelectModelButton assistant={assistant} />}
{/* TODO: Show a select model button for agent. */}
{/* FIXME: models endpoint doesn't return all models, so cannot found. */}
{activeTopicOrSession === 'session' && <ApiModelLabel model={agentModel} />}
{activeTopicOrSession === 'session' && (
<ApiModelLabel classNames={{ container: 'text-xs' }} model={agentModel} />
)}
</HStack>
<HStack alignItems="center" gap={8}>
<UpdateAppButton />

View File

@ -31,7 +31,7 @@ export const AgentSection = () => {
}
return (
<div className="agents-tab h-full w-full">
<div className="agents-tab mb-2 h-full w-full">
<SectionName name={t('common.agent_other')} />
<Agents />
</div>

View File

@ -0,0 +1,88 @@
import { Input, Tooltip } from '@heroui/react'
import { AgentConfiguration, AgentConfigurationSchema, GetAgentResponse, UpdateAgentForm } from '@renderer/types'
import { Info } from 'lucide-react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
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 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 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

@ -1,14 +1,11 @@
import { Button, Chip, Select as HeroSelect, SelectedItems, SelectItem, Tooltip } from '@heroui/react'
import { Button, Input, Select, SelectedItems, SelectItem, Tooltip } from '@heroui/react'
import { loggerService } from '@logger'
import type { Selection } from '@react-types/shared'
import { ApiModelLabel } from '@renderer/components/ApiModelLabel'
import { useApiModels } from '@renderer/hooks/agents/useModels'
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
import { GetAgentResponse, Tool, UpdateAgentForm } from '@renderer/types'
import { Input, Select } from 'antd'
import { DefaultOptionType } from 'antd/es/select'
import { ApiModel, GetAgentResponse, UpdateAgentForm } from '@renderer/types'
import { Plus } from 'lucide-react'
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AgentLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared'
@ -24,9 +21,6 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
const { t } = useTranslation()
const [name, setName] = useState<string>((agent?.name ?? '').trim())
const { models } = useApiModels({ providerType: 'anthropic' })
const availableTools = useMemo(() => agent?.tools ?? [], [agent?.tools])
const [allowedToolIds, setAllowedToolIds] = useState<string[]>([])
const selectedToolKeys = useMemo<Selection>(() => new Set<string>(allowedToolIds), [allowedToolIds])
const updateName = (name: string) => {
if (!agent) return
@ -46,42 +40,6 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
[agent, update]
)
const updateAllowedTools = useCallback(
(allowed_tools: UpdateAgentForm['allowed_tools']) => {
if (!agent) return
update({ id: agent.id, allowed_tools })
},
[agent, update]
)
const modelOptions = useMemo(() => {
return models.map((model) => ({
value: model.id,
label: <ApiModelLabel model={model} />
})) satisfies DefaultOptionType[]
}, [models])
useEffect(() => {
if (!agent) {
setAllowedToolIds((prev) => (prev.length === 0 ? prev : []))
return
}
const allowed = agent.allowed_tools ?? []
const filtered = availableTools.length
? allowed.filter((id) => availableTools.some((tool) => tool.id === id))
: allowed
setAllowedToolIds((prev) => {
const prevSet = new Set(prev)
const isSame = filtered.length === prevSet.size && filtered.every((id) => prevSet.has(id))
if (isSame) {
return prev
}
return filtered
})
}, [agent, availableTools])
const addAccessiblePath = useCallback(async () => {
if (!agent) return
@ -116,53 +74,13 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
[agent, t, updateAccessiblePaths]
)
const renderSelectedTools = useCallback((items: SelectedItems<Tool>) => {
if (!items.length) {
return null
}
return (
<div className="flex flex-wrap gap-2">
{items.map((item) => (
<Chip key={item.key} size="sm" variant="flat" className="max-w-[160px] truncate">
{item.data?.name ?? item.textValue ?? item.key}
</Chip>
))}
</div>
)
const renderModels = useCallback((items: SelectedItems<ApiModel>) => {
return items.map((item) => {
const model = item.data ?? undefined
return <ApiModelLabel key={model?.id} model={model} />
})
}, [])
const onAllowedToolsChange = useCallback(
(keys: Selection) => {
if (!agent) return
const nextIds = keys === 'all' ? availableTools.map((tool) => tool.id) : Array.from(keys).map(String)
const filtered = availableTools.length
? nextIds.filter((id) => availableTools.some((tool) => tool.id === id))
: nextIds
setAllowedToolIds((prev) => {
const prevSet = new Set(prev)
const isSame = filtered.length === prevSet.size && filtered.every((id) => prevSet.has(id))
if (isSame) {
return prev
}
return filtered
})
const previous = agent.allowed_tools ?? []
const previousSet = new Set(previous)
const isSameSelection = filtered.length === previousSet.size && filtered.every((id) => previousSet.has(id))
if (isSameSelection) {
return
}
updateAllowedTools(filtered)
},
[agent, availableTools, updateAllowedTools]
)
if (!agent) return null
return (
@ -176,7 +94,7 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
<Input
placeholder={t('common.agent_one') + t('common.name')}
value={name}
onChange={(e) => setName(e.target.value)}
onValueChange={(value) => setName(value)}
onBlur={() => {
if (name !== agent.name) {
updateName(name)
@ -186,41 +104,24 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
/>
</SettingsItem>
<SettingsItem inline className="gap-8">
<SettingsTitle>{t('common.model')}</SettingsTitle>
<SettingsTitle id="model">{t('common.model')}</SettingsTitle>
<Select
options={modelOptions}
value={agent.model}
onChange={(value) => {
updateModel(value)
selectionMode="single"
aria-labelledby="model"
items={models}
selectedKeys={[agent.model]}
onSelectionChange={(keys) => {
updateModel(keys.currentKey)
}}
className="max-w-80 flex-1"
placeholder={t('common.placeholders.select.model')}
/>
</SettingsItem>
<SettingsItem>
<SettingsTitle>{t('agent.session.allowed_tools.label')}</SettingsTitle>
<HeroSelect
aria-label={t('agent.session.allowed_tools.label')}
selectionMode="multiple"
selectedKeys={selectedToolKeys}
onSelectionChange={onAllowedToolsChange}
placeholder={t('agent.session.allowed_tools.placeholder')}
description={
availableTools.length ? t('agent.session.allowed_tools.helper') : t('agent.session.allowed_tools.empty')
}
isDisabled={!availableTools.length}
items={availableTools}
renderValue={renderSelectedTools}
className="max-w-xl">
{(tool) => (
<SelectItem key={tool.id} textValue={tool.name}>
<div className="flex flex-col">
<span className="font-medium text-sm">{tool.name}</span>
{tool.description ? <span className="text-foreground-500 text-xs">{tool.description}</span> : null}
</div>
renderValue={renderModels}>
{(model) => (
<SelectItem textValue={model.id}>
<ApiModelLabel model={model} />
</SelectItem>
)}
</HeroSelect>
</Select>
</SettingsItem>
<SettingsItem>
<SettingsTitle

View File

@ -0,0 +1,541 @@
import { Alert, Card, CardBody, CardHeader, Chip, Input, Switch } from '@heroui/react'
import { useAgentClient } from '@renderer/hooks/agents/useAgentClient'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import {
AgentConfiguration,
AgentConfigurationSchema,
GetAgentResponse,
PermissionMode,
Tool,
UpdateAgentForm
} from '@renderer/types'
import { Modal } from 'antd'
import { ShieldAlert, ShieldCheck, Wrench } from 'lucide-react'
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { mutate } from 'swr'
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
interface AgentToolingSettingsProps {
agent: GetAgentResponse | undefined | null
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
}
type AgentConfigurationState = AgentConfiguration & Record<string, unknown>
type PermissionModeCard = {
mode: PermissionMode
titleKey: string
titleFallback: string
descriptionKey: string
descriptionFallback: string
behaviorKey: string
behaviorFallback: string
caution?: boolean
unsupported?: boolean
}
const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState
const permissionModeCards: PermissionModeCard[] = [
{
mode: 'default',
titleKey: 'agent.settings.tooling.permissionMode.default.title',
titleFallback: 'Default (ask before continuing)',
descriptionKey: 'agent.settings.tooling.permissionMode.default.description',
descriptionFallback: 'Read-only tools are pre-approved; everything else still needs permission.',
behaviorKey: 'agent.settings.tooling.permissionMode.default.behavior',
behaviorFallback: 'Read-only tools are pre-approved automatically.'
},
{
mode: 'plan',
titleKey: 'agent.settings.tooling.permissionMode.plan.title',
titleFallback: 'Planning mode',
descriptionKey: 'agent.settings.tooling.permissionMode.plan.description',
descriptionFallback: 'Shares the default read-only tool set but presents a plan before execution.',
behaviorKey: 'agent.settings.tooling.permissionMode.plan.behavior',
behaviorFallback: 'Read-only defaults are pre-approved while execution remains disabled.'
},
{
mode: 'acceptEdits',
titleKey: 'agent.settings.tooling.permissionMode.acceptEdits.title',
titleFallback: 'Auto-accept file edits',
descriptionKey: 'agent.settings.tooling.permissionMode.acceptEdits.description',
descriptionFallback: 'File edits and filesystem operations are automatically approved.',
behaviorKey: 'agent.settings.tooling.permissionMode.acceptEdits.behavior',
behaviorFallback: 'Pre-approves trusted filesystem tools so edits run immediately.'
},
{
mode: 'bypassPermissions',
titleKey: 'agent.settings.tooling.permissionMode.bypassPermissions.title',
titleFallback: 'Bypass permission checks',
descriptionKey: 'agent.settings.tooling.permissionMode.bypassPermissions.description',
descriptionFallback: 'All permission prompts are skipped — use with caution.',
behaviorKey: 'agent.settings.tooling.permissionMode.bypassPermissions.behavior',
behaviorFallback: 'Every tool is pre-approved automatically.',
caution: true
}
]
const computeModeDefaults = (mode: PermissionMode, tools: Tool[]): string[] => {
const defaultToolIds = tools.filter((tool) => !tool.requirePermissions).map((tool) => tool.id)
switch (mode) {
case 'acceptEdits':
return [
...defaultToolIds,
'Edit',
'MultiEdit',
'NotebookEdit',
'Write',
'Bash(mkdir:*)',
'Bash(touch:*)',
'Bash(rm:*)',
'Bash(mv:*)',
'Bash(cp:*)'
]
case 'bypassPermissions':
return tools.map((tool) => tool.id)
case 'default':
case 'plan':
return defaultToolIds
}
}
const unique = (values: string[]) => Array.from(new Set(values))
export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, updateAgent }) => {
const { t } = useTranslation()
const client = useAgentClient()
const { mcpServers: allServers } = useMCPServers()
const [configuration, setConfiguration] = useState<AgentConfigurationState>(defaultConfiguration)
const [selectedMode, setSelectedMode] = useState<PermissionMode>(defaultConfiguration.permission_mode)
const [autoToolIds, setAutoToolIds] = useState<string[]>([])
const [approvedToolIds, setApprovedToolIds] = useState<string[]>([])
const [searchTerm, setSearchTerm] = useState('')
const [isUpdatingMode, setIsUpdatingMode] = useState(false)
const [isUpdatingTools, setIsUpdatingTools] = useState(false)
const [selectedMcpIds, setSelectedMcpIds] = useState<string[]>([])
const [isUpdatingMcp, setIsUpdatingMcp] = useState(false)
const availableTools = useMemo(() => agent?.tools ?? [], [agent?.tools])
const availableServers = useMemo(() => allServers ?? [], [allServers])
useEffect(() => {
if (!agent) {
setConfiguration(defaultConfiguration)
setSelectedMode(defaultConfiguration.permission_mode)
setApprovedToolIds([])
setAutoToolIds([])
setSelectedMcpIds([])
return
}
const parsed = AgentConfigurationSchema.parse(agent.configuration ?? {}) as AgentConfigurationState
setConfiguration(parsed)
setSelectedMode(parsed.permission_mode)
const defaults = computeModeDefaults(parsed.permission_mode, availableTools)
setAutoToolIds(defaults)
const allowed = agent.allowed_tools ?? []
setApprovedToolIds((prev) => {
const sanitized = allowed.filter((id) => availableTools.some((tool) => tool.id === id))
const isSame = sanitized.length === prev.length && sanitized.every((id) => prev.includes(id))
if (isSame) {
return prev
}
// Ensure defaults are included even if backend omitted them
const merged = unique([...sanitized, ...defaults])
return merged
})
setSelectedMcpIds(agent.mcps ?? [])
}, [agent, availableTools])
const filteredTools = useMemo(() => {
if (!searchTerm.trim()) {
return availableTools
}
const term = searchTerm.trim().toLowerCase()
return availableTools.filter((tool) => {
return (
tool.name.toLowerCase().includes(term) ||
(tool.description ? tool.description.toLowerCase().includes(term) : false)
)
})
}, [availableTools, searchTerm])
const userAddedIds = useMemo(() => {
return approvedToolIds.filter((id) => !autoToolIds.includes(id))
}, [approvedToolIds, autoToolIds])
const handleSelectPermissionMode = useCallback(
(nextMode: PermissionMode) => {
if (!agent || nextMode === selectedMode || isUpdatingMode) {
return
}
const defaults = computeModeDefaults(nextMode, availableTools)
const merged = unique([...defaults, ...userAddedIds])
const removedDefaults = autoToolIds.filter((id) => !defaults.includes(id))
const applyChange = async () => {
setIsUpdatingMode(true)
try {
const nextConfiguration = { ...configuration, permission_mode: nextMode } satisfies AgentConfigurationState
await updateAgent({ id: agent.id, configuration: nextConfiguration, allowed_tools: merged })
setConfiguration(nextConfiguration)
setSelectedMode(nextMode)
setAutoToolIds(defaults)
setApprovedToolIds(merged)
} finally {
setIsUpdatingMode(false)
}
}
if (removedDefaults.length > 0) {
Modal.confirm({
title: t('agent.settings.tooling.permissionMode.confirmChange.title', 'Change permission mode?'),
content: (
<div className="flex flex-col gap-2">
<p className="text-foreground-500 text-sm">
{t(
'agent.settings.tooling.permissionMode.confirmChange.description',
'Switching modes updates the automatically approved tools.'
)}
</p>
<div className="rounded-medium border border-default-200 bg-default-50 px-3 py-2 text-sm">
<span className="font-medium text-foreground">{t('common.removed', 'Removed')}:</span>
<ul className="mt-1 list-disc pl-4">
{removedDefaults.map((id) => {
const tool = availableTools.find((item) => item.id === id)
return <li key={id}>{tool?.name ?? id}</li>
})}
</ul>
</div>
</div>
),
okText: t('common.confirm'),
cancelText: t('common.cancel'),
onOk: applyChange
})
} else {
void applyChange()
}
},
[agent, selectedMode, isUpdatingMode, availableTools, userAddedIds, autoToolIds, configuration, updateAgent, t]
)
const handleToggleTool = useCallback(
(toolId: string, isApproved: boolean) => {
if (!agent || isUpdatingTools) {
return
}
setApprovedToolIds((prev) => {
const exists = prev.includes(toolId)
if (isApproved === exists) {
return prev
}
const next = isApproved ? [...prev, toolId] : prev.filter((id) => id !== toolId)
const sanitized = unique(next.filter((id) => availableTools.some((tool) => tool.id === id)).concat(autoToolIds))
setIsUpdatingTools(true)
void (async () => {
try {
await updateAgent({ id: agent.id, allowed_tools: sanitized })
} finally {
setIsUpdatingTools(false)
}
})()
return sanitized
})
},
[agent, isUpdatingTools, availableTools, autoToolIds, updateAgent]
)
const { agentSummary, autoCount, customCount } = useMemo(() => {
const autoCountValue = autoToolIds.length
const customCountValue = userAddedIds.length
return {
agentSummary: {
mode: selectedMode,
auto: autoCountValue,
custom: customCountValue,
totalTools: availableTools.length,
mcps: selectedMcpIds.length
},
autoCount: autoCountValue,
customCount: customCountValue
}
}, [selectedMode, autoToolIds, userAddedIds, availableTools.length, selectedMcpIds.length])
const handleToggleMcp = useCallback(
(serverId: string, enabled: boolean) => {
if (!agent || isUpdatingMcp) {
return
}
setSelectedMcpIds((prev) => {
const exists = prev.includes(serverId)
if (enabled === exists) {
return prev
}
const next = enabled ? [...prev, serverId] : prev.filter((id) => id !== serverId)
setIsUpdatingMcp(true)
void (async () => {
try {
await updateAgent({ id: agent.id, mcps: next })
const refreshed = await client.getAgent(agent.id)
const key = client.agentPaths.withId(agent.id)
mutate(key, refreshed, false)
} finally {
setIsUpdatingMcp(false)
}
})()
return next
})
},
[agent, isUpdatingMcp, client, updateAgent]
)
if (!agent) {
return null
}
return (
<SettingsContainer>
<SettingsItem>
<SettingsTitle>
{t('agent.settings.tooling.steps.permissionMode.title', 'Step 1 · Permission mode')}
</SettingsTitle>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
{permissionModeCards.map((card) => {
const isSelected = card.mode === selectedMode
const disabled = card.unsupported
const showCaution = card.caution
return (
<Card
key={card.mode}
isPressable={!disabled}
isDisabled={disabled || isUpdatingMode}
onPress={() => handleSelectPermissionMode(card.mode)}
className={`border ${
isSelected ? 'border-primary shadow-lg' : 'border-default-200'
} ${disabled ? 'opacity-60' : ''}`}>
<CardHeader className="flex items-start justify-between gap-2">
<div className="flex flex-col">
<span className="text-left font-semibold text-sm">{t(card.titleKey, card.titleFallback)}</span>
<span className="text-left text-foreground-500 text-xs">
{t(card.descriptionKey, card.descriptionFallback)}
</span>
</div>
{disabled ? (
<Chip color="warning" size="sm" variant="flat">
{t('common.coming_soon', 'Coming soon')}
</Chip>
) : isSelected ? (
<Chip color="primary" size="sm" variant="flat" startContent={<ShieldCheck size={14} />}>
{t('common.selected', 'Selected')}
</Chip>
) : null}
</CardHeader>
<CardBody className="gap-2 text-left text-xs">
<span className="text-foreground-600">{t(card.behaviorKey, card.behaviorFallback)}</span>
{showCaution ? (
<span className="flex items-center gap-1 text-danger-600">
<ShieldAlert size={14} />
{t(
'agent.settings.tooling.permissionMode.bypassPermissions.warning',
'Use with caution — all tools will run without asking for approval.'
)}
</span>
) : null}
</CardBody>
</Card>
)
})}
</div>
</SettingsItem>
<SettingsItem>
<SettingsTitle>
{t('agent.settings.tooling.steps.preapproved.title', 'Step 2 · Pre-approved tools')}
</SettingsTitle>
<div className="flex flex-col gap-4">
<Alert
color="warning"
title={t(
'agent.settings.tooling.preapproved.warning.title',
'Pre-approved tools run without manual review.'
)}
description={t(
'agent.settings.tooling.preapproved.warning.description',
'Enable only tools you trust. Mode defaults are highlighted automatically.'
)}
/>
<Input
isClearable
value={searchTerm}
onValueChange={setSearchTerm}
placeholder={t('agent.settings.tooling.preapproved.search', 'Search tools')}
aria-label={t('agent.settings.tooling.preapproved.search', 'Search tools')}
className="max-w-md"
/>
<div className="flex flex-col gap-3">
{filteredTools.length === 0 ? (
<div className="rounded-medium border border-default-200 border-dashed px-4 py-10 text-center text-foreground-500 text-sm">
{t('agent.settings.tooling.preapproved.empty', 'No tools match your filters.')}
</div>
) : (
filteredTools.map((tool) => {
const isAuto = autoToolIds.includes(tool.id)
const isApproved = approvedToolIds.includes(tool.id)
return (
<Card key={tool.id} shadow="none" className="border border-default-200">
<CardHeader className="flex items-start justify-between gap-3">
<div className="flex min-w-0 flex-col gap-1">
<span className="truncate font-medium text-sm">{tool.name}</span>
{tool.description ? (
<span className="line-clamp-2 text-foreground-500 text-xs">{tool.description}</span>
) : null}
<div className="flex flex-wrap items-center gap-2">
{isAuto ? (
<Chip size="sm" color="primary" variant="flat">
{t('agent.settings.tooling.preapproved.autoBadge', 'Added by mode')}
</Chip>
) : null}
{tool.type === 'mcp' ? (
<Chip size="sm" color="secondary" variant="flat">
{t('agent.settings.tooling.preapproved.mcpBadge', 'MCP tool')}
</Chip>
) : null}
{tool.requirePermissions ? (
<Chip size="sm" color="warning" variant="flat">
{t(
'agent.settings.tooling.preapproved.requiresApproval',
'Requires approval when disabled'
)}
</Chip>
) : null}
</div>
</div>
<Switch
aria-label={t('agent.settings.tooling.preapproved.toggle', {
defaultValue: `Toggle ${tool.name}`,
name: tool.name
})}
isSelected={isApproved}
isDisabled={isAuto || isUpdatingTools}
size="sm"
onValueChange={(value) => handleToggleTool(tool.id, value)}
/>
</CardHeader>
{isAuto ? (
<CardBody className="py-0 pb-3">
<span className="text-foreground-400 text-xs">
{t(
'agent.settings.tooling.preapproved.autoDescription',
'This tool is auto-approved by the current permission mode.'
)}
</span>
</CardBody>
) : null}
</Card>
)
})
)}
</div>
</div>
</SettingsItem>
<SettingsItem>
<SettingsTitle>{t('agent.settings.tooling.steps.mcp.title', 'MCP servers')}</SettingsTitle>
<div className="flex flex-col gap-3">
<span className="text-foreground-500 text-sm">
{t(
'agent.settings.tooling.mcp.description',
'Connect MCP servers to unlock additional tools you can approve above.'
)}
</span>
{availableServers.length === 0 ? (
<div className="rounded-medium border border-default-200 border-dashed px-4 py-6 text-center text-foreground-500 text-sm">
{t('agent.settings.tooling.mcp.empty', 'No MCP servers detected. Add one from the MCP settings page.')}
</div>
) : (
<div className="flex flex-col gap-2">
{availableServers.map((server) => {
const isSelected = selectedMcpIds.includes(server.id)
return (
<Card key={server.id} shadow="none" className="border border-default-200">
<CardHeader className="flex items-center justify-between gap-2">
<div className="flex min-w-0 flex-col">
<span className="truncate font-medium text-sm">{server.name}</span>
{server.description ? (
<span className="line-clamp-2 text-foreground-500 text-xs">{server.description}</span>
) : null}
</div>
<Switch
aria-label={t('agent.settings.tooling.mcp.toggle', {
defaultValue: `Toggle ${server.name}`,
name: server.name
})}
isSelected={isSelected}
size="sm"
isDisabled={!server.isActive || isUpdatingMcp}
onValueChange={(value) => handleToggleMcp(server.id, value)}
/>
</CardHeader>
</Card>
)
})}
</div>
)}
<div className="flex items-center gap-2 text-foreground-500 text-xs">
<Wrench size={14} />
<span>
{t('agent.settings.tooling.mcp.manageHint', 'Need advanced configuration? Visit Settings → MCP Servers.')}
</span>
</div>
</div>
</SettingsItem>
<SettingsItem divider={false}>
<SettingsTitle>{t('agent.settings.tooling.steps.review.title', 'Step 3 · Review')}</SettingsTitle>
<Card shadow="none" className="border border-default-200">
<CardBody className="flex flex-col gap-2 text-sm">
<div className="flex flex-wrap gap-3">
<Chip variant="flat" color="primary">
{t('agent.settings.tooling.review.mode', {
defaultValue: `Mode: ${selectedMode}`,
mode: selectedMode
})}
</Chip>
<Chip variant="flat" color="secondary">
{t('agent.settings.tooling.review.autoTools', {
defaultValue: `Auto: ${autoCount}`,
count: autoCount
})}
</Chip>
<Chip variant="flat" color="success">
{t('agent.settings.tooling.review.customTools', {
defaultValue: `Custom: ${customCount}`,
count: customCount
})}
</Chip>
<Chip variant="flat" color="warning">
{t('agent.settings.tooling.review.mcp', {
defaultValue: `MCP: ${agentSummary.mcps}`,
count: agentSummary.mcps
})}
</Chip>
</div>
<span className="text-foreground-500 text-xs">
{t(
'agent.settings.tooling.review.helper',
'Changes save automatically. Adjust the steps above any time to fine-tune permissions.'
)}
</span>
</CardBody>
</Card>
</SettingsItem>
</SettingsContainer>
)
}
export default AgentToolingSettings

View File

@ -7,8 +7,10 @@ import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import AgentAdvanceSettings from './AgentAdvanceSettings'
import AgentEssentialSettings from './AgentEssentialSettings'
import AgentPromptSettings from './AgentPromptSettings'
import AgentToolingSettings from './AgentToolingSettings'
import { AgentLabel } from './shared'
interface AgentSettingPopupShowParams {
@ -20,7 +22,7 @@ interface AgentSettingPopupParams extends AgentSettingPopupShowParams {
resolve: () => void
}
type AgentSettingPopupTab = 'essential' | 'prompt'
type AgentSettingPopupTab = 'essential' | 'prompt' | 'tooling' | 'advance' | 'session-mcps'
const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, agentId, resolve }) => {
const [open, setOpen] = useState(true)
@ -51,6 +53,14 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
{
key: 'prompt',
label: t('agent.settings.prompt')
},
{
key: 'tooling',
label: t('agent.settings.tooling.tab', 'Tooling & permissions')
},
{
key: 'advance',
label: t('agent.settings.advance.title', 'Advanced Settings')
}
] as const satisfies { key: AgentSettingPopupTab; label: string }[]
).filter(Boolean)
@ -81,6 +91,8 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
<Settings>
{menu === 'essential' && <AgentEssentialSettings agent={agent} update={updateAgent} />}
{menu === 'prompt' && <AgentPromptSettings agent={agent} update={updateAgent} />}
{menu === 'tooling' && <AgentToolingSettings agent={agent} updateAgent={updateAgent} />}
{menu === 'advance' && <AgentAdvanceSettings agent={agent} updateAgent={updateAgent} />}
</Settings>
</div>
)

View File

@ -45,6 +45,7 @@ export type Tool = z.infer<typeof ToolSchema>
// ------------------ Agent configuration & base schema ------------------
export const AgentConfigurationSchema = z
.object({
// https://docs.claude.com/en/docs/claude-code/sdk/sdk-permissions#mode-specific-behaviors
permission_mode: PermissionModeSchema.default('default'), // Permission mode, default to 'default'
max_turns: z.number().default(10) // Maximum number of interaction turns, default to 10
})
@ -156,8 +157,6 @@ export interface AgentMessagePersistExchangeResult {
// Not implemented fields:
// - plan_model: Optional model for planning/thinking tasks
// - small_model: Optional lightweight model for quick responses
// - mcps: Optional array of MCP (Model Control Protocol) tool IDs
// - allowed_tools: Optional array of permitted tool IDs
// - configuration: Optional agent settings (temperature, top_p, etc.)
// ------------------ Form models ------------------
export type BaseAgentForm = {
@ -170,6 +169,8 @@ export type BaseAgentForm = {
model: string
accessible_paths: string[]
allowed_tools: string[]
mcps?: string[]
configuration?: AgentConfiguration
}
export type AddAgentForm = Omit<BaseAgentForm, 'id'> & { id?: never }