mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-03 11:19:10 +08:00
♻️ refactor: improve agent tool approval UI with dedicated settings tab
- Move tool selection from essential settings to dedicated "Pre-approved tools" tab - Update terminology from "Allowed tools" to "Pre-approved tools" for clarity - Add new AgentToolSettings component with enhanced card-based layout - Include warning alert about pre-approved tools bypassing review - Update all language files with new terminology and translation keys - Add i18n sync guidance to CLAUDE.md development commands
This commit is contained in:
parent
9c679ede20
commit
7ca9dcd2fb
@ -24,7 +24,7 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **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)
|
||||
- **Build Check**: `yarn build:check` - REQUIRED before commits (lint + test + typecheck), if having i18n sort issues, run `yarn i18n:sync` first to sync template.
|
||||
- **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
|
||||
|
||||
@ -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": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "Essential Settings",
|
||||
"mcps": "MCP Servers",
|
||||
"prompt": "Prompt Settings"
|
||||
"prompt": "Prompt Settings",
|
||||
"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",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "当前智能体暂无可用工具。",
|
||||
"helper": "选择预先授权的工具,未选中的工具在使用时需要手动审批。",
|
||||
"label": "允许的工具",
|
||||
"placeholder": "选择允许使用的工具"
|
||||
"label": "预先授权工具",
|
||||
"placeholder": "选择预先授权的工具"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "基础设置",
|
||||
"mcps": "MCP 服务器",
|
||||
"prompt": "提示词设置"
|
||||
"prompt": "提示词设置",
|
||||
"tools": {
|
||||
"approved": "已授权",
|
||||
"caution": "预先授权的工具会跳过人工审核,请仅启用可信的工具。",
|
||||
"description": "选择哪些工具可以在无需人工审批的情况下执行。",
|
||||
"requiresPermission": "未预先授权时需要人工审批。",
|
||||
"tab": "预先授权工具",
|
||||
"title": "预先授权工具",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "智能体类型",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "目前此代理沒有可用的工具。",
|
||||
"helper": "選擇預先授權的工具,未選取的工具在使用時需要手動審批。",
|
||||
"label": "允許的工具",
|
||||
"placeholder": "選擇允許使用的工具"
|
||||
"label": "預先授權工具",
|
||||
"placeholder": "選擇預先授權的工具"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "必要設定",
|
||||
"mcps": "MCP 伺服器",
|
||||
"prompt": "提示設定"
|
||||
"prompt": "提示設定",
|
||||
"tools": {
|
||||
"approved": "已授權",
|
||||
"caution": "預先授權的工具會略過人工審查,請僅啟用可信任的工具。",
|
||||
"description": "選擇哪些工具可在無需人工核准的情況下執行。",
|
||||
"requiresPermission": "未預先授權時需要人工核准。",
|
||||
"tab": "預先授權工具",
|
||||
"title": "預先授權工具",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "代理類型",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "Δεν υπάρχουν διαθέσιμα εργαλεία για αυτόν τον agent.",
|
||||
"helper": "Επιλέξτε ποια εργαλεία είναι προεγκεκριμένα. Τα μη επιλεγμένα θα χρειαστούν έγκριση πριν από τη χρήση.",
|
||||
"label": "Επιτρεπόμενα εργαλεία",
|
||||
"placeholder": "Επιλέξτε επιτρεπόμενα εργαλεία"
|
||||
"label": "[to be translated]:Pre-approved tools",
|
||||
"placeholder": "[to be translated]:Select pre-approved tools"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "Βασικές Ρυθμίσεις",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "Ρυθμίσεις Προτροπής"
|
||||
"prompt": "Ρυθμίσεις Προτροπής",
|
||||
"tools": {
|
||||
"approved": "[to be translated]:approved",
|
||||
"caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.",
|
||||
"description": "[to be translated]:Choose which tools can run without manual approval.",
|
||||
"requiresPermission": "[to be translated]:Requires permission when not pre-approved.",
|
||||
"tab": "[to be translated]:Pre-approved tools",
|
||||
"title": "[to be translated]:Pre-approved tools",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Τύπος Πράκτορα",
|
||||
|
||||
@ -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": "[to be translated]:Pre-approved tools",
|
||||
"placeholder": "[to be translated]:Select pre-approved tools"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "Configuraciones esenciales",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "Configuración de indicaciones"
|
||||
"prompt": "Configuración de indicaciones",
|
||||
"tools": {
|
||||
"approved": "[to be translated]:approved",
|
||||
"caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.",
|
||||
"description": "[to be translated]:Choose which tools can run without manual approval.",
|
||||
"requiresPermission": "[to be translated]:Requires permission when not pre-approved.",
|
||||
"tab": "[to be translated]:Pre-approved tools",
|
||||
"title": "[to be translated]:Pre-approved tools",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Tipo de Agente",
|
||||
|
||||
@ -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": "[to be translated]:Pre-approved tools",
|
||||
"placeholder": "[to be translated]:Select pre-approved tools"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "Paramètres essentiels",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "Paramètres de l'invite"
|
||||
"prompt": "Paramètres de l'invite",
|
||||
"tools": {
|
||||
"approved": "[to be translated]:approved",
|
||||
"caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.",
|
||||
"description": "[to be translated]:Choose which tools can run without manual approval.",
|
||||
"requiresPermission": "[to be translated]:Requires permission when not pre-approved.",
|
||||
"tab": "[to be translated]:Pre-approved tools",
|
||||
"title": "[to be translated]:Pre-approved tools",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Type d'agent",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "このエージェントが利用できるツールはありません。",
|
||||
"helper": "事前承認済みのツールを選択します。未選択のツールは使用時に承認が必要になります。",
|
||||
"label": "許可されたツール",
|
||||
"placeholder": "許可するツールを選択"
|
||||
"label": "事前承認済みツール",
|
||||
"placeholder": "事前承認するツールを選択"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "必須設定",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "プロンプト設定"
|
||||
"prompt": "プロンプト設定",
|
||||
"tools": {
|
||||
"approved": "承認済み",
|
||||
"caution": "事前承認したツールは人によるレビューをスキップします。信頼できるツールのみ有効にしてください。",
|
||||
"description": "人による承認なしで実行できるツールを選択します。",
|
||||
"requiresPermission": "事前承認されていない場合は承認が必要です。",
|
||||
"tab": "事前承認済みツール",
|
||||
"title": "事前承認済みツール",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "エージェントタイプ",
|
||||
|
||||
@ -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": "[to be translated]:Pre-approved tools",
|
||||
"placeholder": "[to be translated]:Select pre-approved tools"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "Configurações Essenciais",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "Configurações de Prompt"
|
||||
"prompt": "Configurações de Prompt",
|
||||
"tools": {
|
||||
"approved": "[to be translated]:approved",
|
||||
"caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.",
|
||||
"description": "[to be translated]:Choose which tools can run without manual approval.",
|
||||
"requiresPermission": "[to be translated]:Requires permission when not pre-approved.",
|
||||
"tab": "[to be translated]:Pre-approved tools",
|
||||
"title": "[to be translated]:Pre-approved tools",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Tipo de Agente",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "Для этого агента нет доступных инструментов.",
|
||||
"helper": "Выберите инструменты с предварительным допуском. Неотмеченные инструменты потребуют подтверждения перед использованием.",
|
||||
"label": "Разрешённые инструменты",
|
||||
"placeholder": "Выберите разрешённые инструменты"
|
||||
"label": "[to be translated]:Pre-approved tools",
|
||||
"placeholder": "[to be translated]:Select pre-approved tools"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -101,7 +101,16 @@
|
||||
},
|
||||
"essential": "Основные настройки",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "Настройки подсказки"
|
||||
"prompt": "Настройки подсказки",
|
||||
"tools": {
|
||||
"approved": "[to be translated]:approved",
|
||||
"caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.",
|
||||
"description": "[to be translated]:Choose which tools can run without manual approval.",
|
||||
"requiresPermission": "[to be translated]:Requires permission when not pre-approved.",
|
||||
"tab": "[to be translated]:Pre-approved tools",
|
||||
"title": "[to be translated]:Pre-approved tools",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Тип агента",
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { Button, Chip, Select as HeroSelect, SelectedItems, SelectItem, Tooltip } from '@heroui/react'
|
||||
import { Button, 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 { GetAgentResponse, UpdateAgentForm } from '@renderer/types'
|
||||
import { Input, Select } from 'antd'
|
||||
import { DefaultOptionType } from 'antd/es/select'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { FC, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { AgentLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||
@ -24,9 +23,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,14 +42,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,
|
||||
@ -61,27 +49,6 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
|
||||
})) 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,52 +83,6 @@ 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 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
|
||||
|
||||
@ -197,31 +118,6 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
|
||||
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>
|
||||
</SelectItem>
|
||||
)}
|
||||
</HeroSelect>
|
||||
</SettingsItem>
|
||||
<SettingsItem>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
|
||||
@ -0,0 +1,149 @@
|
||||
import { Alert, Card, CardBody, CardHeader, Switch, Tooltip } from '@heroui/react'
|
||||
import { GetAgentResponse, Tool, UpdateAgentForm } from '@renderer/types'
|
||||
import { Info } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
interface AgentToolSettingsProps {
|
||||
agent: GetAgentResponse | undefined | null
|
||||
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
|
||||
}
|
||||
|
||||
const isSameSelection = (next: string[], previous: string[]) => {
|
||||
if (next.length !== previous.length) {
|
||||
return false
|
||||
}
|
||||
const previousSet = new Set(previous)
|
||||
return next.every((id) => previousSet.has(id))
|
||||
}
|
||||
|
||||
export const AgentToolSettings: FC<AgentToolSettingsProps> = ({ agent, updateAgent }) => {
|
||||
const { t } = useTranslation()
|
||||
const [approvedIds, setApprovedIds] = useState<string[]>([])
|
||||
|
||||
const availableTools = useMemo<Tool[]>(() => agent?.tools ?? [], [agent?.tools])
|
||||
|
||||
useEffect(() => {
|
||||
if (!agent) {
|
||||
setApprovedIds((prev) => (prev.length === 0 ? prev : []))
|
||||
return
|
||||
}
|
||||
const allowed = agent.allowed_tools ?? []
|
||||
const validIds = allowed.filter((id) => availableTools.some((tool) => tool.id === id))
|
||||
setApprovedIds((prev) => {
|
||||
if (isSameSelection(prev, validIds)) {
|
||||
return prev
|
||||
}
|
||||
return validIds
|
||||
})
|
||||
}, [agent, availableTools])
|
||||
|
||||
const handleToggle = useCallback(
|
||||
(toolId: string, isApproved: boolean) => {
|
||||
if (!agent) return
|
||||
|
||||
setApprovedIds((prev) => {
|
||||
const exists = prev.includes(toolId)
|
||||
if (isApproved === exists) {
|
||||
return prev
|
||||
}
|
||||
const next = isApproved ? [...prev, toolId] : prev.filter((id) => id !== toolId)
|
||||
const previous = agent.allowed_tools ?? []
|
||||
if (!isSameSelection(next, previous)) {
|
||||
updateAgent({ id: agent.id, allowed_tools: next })
|
||||
}
|
||||
return next
|
||||
})
|
||||
},
|
||||
[agent, updateAgent]
|
||||
)
|
||||
|
||||
const approvedCount = useMemo(() => {
|
||||
return approvedIds.filter((id) => availableTools.some((tool) => tool.id === id)).length
|
||||
}, [approvedIds, availableTools])
|
||||
|
||||
if (!agent) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<SettingsItem divider={false} className="flex-1">
|
||||
<div className="flex h-full flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<SettingsTitle>
|
||||
{t('agent.settings.tools.title', 'Pre-approved tools')}
|
||||
<Tooltip
|
||||
placement="right"
|
||||
content={t(
|
||||
'agent.settings.tools.description',
|
||||
'Choose which tools can run without manual approval.'
|
||||
)}>
|
||||
<Info size={16} className="text-foreground-400" />
|
||||
</Tooltip>
|
||||
</SettingsTitle>
|
||||
{availableTools.length > 0 ? (
|
||||
<span className="text-foreground-500 text-xs">
|
||||
{approvedCount} / {availableTools.length} {t('agent.settings.tools.approved', 'approved')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<Alert
|
||||
color="warning"
|
||||
title={t(
|
||||
'agent.settings.tools.caution',
|
||||
'Pre-approved tools bypass human review. Enable only trusted tools.'
|
||||
)}
|
||||
/>
|
||||
|
||||
{availableTools.length > 0 ? (
|
||||
<div className="flex flex-1 flex-col gap-3 overflow-auto pr-1">
|
||||
{availableTools.map((tool) => {
|
||||
const isApproved = approvedIds.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-1 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>
|
||||
<Switch
|
||||
aria-label={t('agent.settings.tools.toggle', {
|
||||
defaultValue: `Toggle ${tool.name}`
|
||||
})}
|
||||
isSelected={isApproved}
|
||||
size="sm"
|
||||
onValueChange={(value) => handleToggle(tool.id, value)}
|
||||
/>
|
||||
</CardHeader>
|
||||
{tool.requirePermissions ? (
|
||||
<CardBody className="py-0 pb-3">
|
||||
<span className="text-foreground-400 text-xs">
|
||||
{t(
|
||||
'agent.settings.tools.requiresPermission',
|
||||
'Requires permission when not pre-approved.'
|
||||
)}
|
||||
</span>
|
||||
</CardBody>
|
||||
) : null}
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center rounded-medium border border-default-200 border-dashed px-4 py-10 text-foreground-500 text-sm">
|
||||
{t('agent.session.allowed_tools.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</SettingsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentToolSettings
|
||||
@ -11,6 +11,7 @@ import AgentAdvanceSettings from './AgentAdvanceSettings'
|
||||
import AgentEssentialSettings from './AgentEssentialSettings'
|
||||
import AgentMCPSettings from './AgentMCPSettings'
|
||||
import AgentPromptSettings from './AgentPromptSettings'
|
||||
import AgentToolSettings from './AgentToolSettings'
|
||||
import { AgentLabel } from './shared'
|
||||
|
||||
interface AgentSettingPopupShowParams {
|
||||
@ -22,7 +23,7 @@ interface AgentSettingPopupParams extends AgentSettingPopupShowParams {
|
||||
resolve: () => void
|
||||
}
|
||||
|
||||
type AgentSettingPopupTab = 'essential' | 'prompt' | 'mcps' | 'advance' | 'session-mcps'
|
||||
type AgentSettingPopupTab = 'essential' | 'prompt' | 'tools' | 'mcps' | 'advance' | 'session-mcps'
|
||||
|
||||
const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, agentId, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
@ -54,6 +55,10 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
|
||||
key: 'prompt',
|
||||
label: t('agent.settings.prompt')
|
||||
},
|
||||
{
|
||||
key: 'tools',
|
||||
label: t('agent.settings.tools.tab', 'Pre-approved tools')
|
||||
},
|
||||
{
|
||||
key: 'mcps',
|
||||
label: t('agent.settings.mcps', 'MCP Servers')
|
||||
@ -91,6 +96,7 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
|
||||
<Settings>
|
||||
{menu === 'essential' && <AgentEssentialSettings agent={agent} update={updateAgent} />}
|
||||
{menu === 'prompt' && <AgentPromptSettings agent={agent} update={updateAgent} />}
|
||||
{menu === 'tools' && <AgentToolSettings agent={agent} updateAgent={updateAgent} />}
|
||||
{menu === 'mcps' && <AgentMCPSettings agent={agent} updateAgent={updateAgent} />}
|
||||
{menu === 'advance' && <AgentAdvanceSettings agent={agent} updateAgent={updateAgent} />}
|
||||
</Settings>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user