mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 05:11:24 +08:00
Merge branch 'feat/agents-new' of github.com:CherryHQ/cherry-studio into feat/agents-new
This commit is contained in:
commit
164386a337
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
@ -251,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)
|
||||
@ -263,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) {
|
||||
@ -285,6 +288,7 @@ export const SessionModal: React.FC<Props> = ({
|
||||
form.instructions,
|
||||
form.accessible_paths,
|
||||
form.allowed_tools,
|
||||
form.mcps,
|
||||
session,
|
||||
onClose,
|
||||
onSessionCreated,
|
||||
|
||||
@ -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,37 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
"mcps": "MCP Servers",
|
||||
"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": {
|
||||
@ -80,8 +80,37 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"advance": {
|
||||
"maxTurns": {
|
||||
"description": "设定代理自动执行的请求/回复轮次数。",
|
||||
"helper": "数值越高可自主运行越久;数值越低更易控制。",
|
||||
"label": "会话轮次数上限"
|
||||
},
|
||||
"permissionMode": {
|
||||
"description": "控制代理在需要授权时的处理方式。",
|
||||
"label": "权限模式",
|
||||
"options": {
|
||||
"acceptEdits": "自动接受编辑",
|
||||
"bypassPermissions": "跳过权限检查",
|
||||
"default": "默认(继续前询问)",
|
||||
"plan": "规划模式(需审批计划)"
|
||||
},
|
||||
"placeholder": "选择权限模式"
|
||||
},
|
||||
"title": "高级设置"
|
||||
},
|
||||
"essential": "基础设置",
|
||||
"prompt": "提示词设置"
|
||||
"mcps": "MCP 服务器",
|
||||
"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": {
|
||||
@ -80,8 +80,37 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"advance": {
|
||||
"maxTurns": {
|
||||
"description": "設定代理自動執行的請求/回覆輪次数。",
|
||||
"helper": "數值越高可自動運行越久;數值越低更容易掌控。",
|
||||
"label": "會話輪次上限"
|
||||
},
|
||||
"permissionMode": {
|
||||
"description": "控制代理在需要授權時的處理方式。",
|
||||
"label": "權限模式",
|
||||
"options": {
|
||||
"acceptEdits": "自動接受編輯",
|
||||
"bypassPermissions": "略過權限檢查",
|
||||
"default": "預設(繼續前先詢問)",
|
||||
"plan": "規劃模式(需核准計畫)"
|
||||
},
|
||||
"placeholder": "選擇權限模式"
|
||||
},
|
||||
"title": "進階設定"
|
||||
},
|
||||
"essential": "必要設定",
|
||||
"prompt": "提示設定"
|
||||
"mcps": "MCP 伺服器",
|
||||
"prompt": "提示設定",
|
||||
"tools": {
|
||||
"approved": "已授權",
|
||||
"caution": "預先授權的工具會略過人工審查,請僅啟用可信任的工具。",
|
||||
"description": "選擇哪些工具可在無需人工核准的情況下執行。",
|
||||
"requiresPermission": "未預先授權時需要人工核准。",
|
||||
"tab": "預先授權工具",
|
||||
"title": "預先授權工具",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "代理類型",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "Δεν υπάρχουν διαθέσιμα εργαλεία για αυτόν τον agent.",
|
||||
"helper": "Επιλέξτε ποια εργαλεία είναι προεγκεκριμένα. Τα μη επιλεγμένα θα χρειαστούν έγκριση πριν από τη χρήση.",
|
||||
"label": "Επιτρεπόμενα εργαλεία",
|
||||
"placeholder": "Επιλέξτε επιτρεπόμενα εργαλεία"
|
||||
"label": "Προεγκεκριμένα εργαλεία",
|
||||
"placeholder": "Επιλέξτε προεγκεκριμένα εργαλεία"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -80,8 +80,37 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"advance": {
|
||||
"maxTurns": {
|
||||
"description": "Ορίστε τον αριθμό γύρων αιτήματος/απάντησης που θα εκτελούνται αυτόματα από τον διαμεσολαβητ.",
|
||||
"helper": "Όσο υψηλότερη είναι η τιμή, τόσο περισσότερο μπορεί να λειτουργεί αυτόνομα· όσο χαμηλότερη είναι, τόσο πιο εύκολα ελέγχεται.",
|
||||
"label": "Όριο αριθμού γύρων συνεδρίας"
|
||||
},
|
||||
"permissionMode": {
|
||||
"description": "Ο τρόπος με τον οποίο ο πληρεξούσιος χειρίζεται την κατάσταση όταν απαιτείται εξουσιοδότηση.",
|
||||
"label": "Λειτουργία δικαιωμάτων",
|
||||
"options": {
|
||||
"acceptEdits": "Αυτόματη αποδοχή επεξεργασίας",
|
||||
"bypassPermissions": "παράλειψη ελέγχου δικαιωμάτων",
|
||||
"default": "Προεπιλογή (να ερωτηθεί πριν από τη συνέχεια)",
|
||||
"plan": "Λειτουργία σχεδιασμού (απαιτείται έγκριση σχεδίου)"
|
||||
},
|
||||
"placeholder": "Επιλέξτε λειτουργία δικαιωμάτων"
|
||||
},
|
||||
"title": "Ρυθμίσεις για προχωρημένους"
|
||||
},
|
||||
"essential": "Βασικές Ρυθμίσεις",
|
||||
"prompt": "Ρυθμίσεις Προτροπής"
|
||||
"mcps": "Διακομιστής MCP",
|
||||
"prompt": "Ρυθμίσεις Προτροπής",
|
||||
"tools": {
|
||||
"approved": "εγκεκριμένο",
|
||||
"caution": "Εργαλεία προεγκεκριμένα παρακάμπτουν την ανθρώπινη αξιολόγηση. Ενεργοποιήστε μόνο έμπιστα εργαλεία.",
|
||||
"description": "Επιλέξτε ποια εργαλεία μπορούν να εκτελούνται χωρίς χειροκίνητη έγκριση.",
|
||||
"requiresPermission": "Απαιτείται άδεια όταν δεν έχει προεγκριθεί.",
|
||||
"tab": "Προεγκεκριμένα εργαλεία",
|
||||
"title": "Προεγκεκριμένα εργαλεία",
|
||||
"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": "Herramientas preaprobadas",
|
||||
"placeholder": "Seleccionar herramientas preaprobadas"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -80,8 +80,37 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
|
||||
@ -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,37 @@
|
||||
}
|
||||
},
|
||||
"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": "sauter 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",
|
||||
"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",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "このエージェントが利用できるツールはありません。",
|
||||
"helper": "事前承認済みのツールを選択します。未選択のツールは使用時に承認が必要になります。",
|
||||
"label": "許可されたツール",
|
||||
"placeholder": "許可するツールを選択"
|
||||
"label": "事前承認済みツール",
|
||||
"placeholder": "事前承認するツールを選択"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -80,8 +80,37 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"advance": {
|
||||
"maxTurns": {
|
||||
"description": "プロキシが自動的に実行するリクエスト/レスポンスのラウンド数を設定します。",
|
||||
"helper": "数値が高いほど自律動作の時間が長くなり、数値が低いほど制御しやすくなります。",
|
||||
"label": "会話ラウンド数の上限"
|
||||
},
|
||||
"permissionMode": {
|
||||
"description": "制御エージェントが認可を必要とする場合の処理方法。",
|
||||
"label": "権限モード",
|
||||
"options": {
|
||||
"acceptEdits": "自動的に編集を受け入れる",
|
||||
"bypassPermissions": "権限チェックをスキップ",
|
||||
"default": "デフォルト(続行する前に確認)",
|
||||
"plan": "計画モード(承認が必要な計画)"
|
||||
},
|
||||
"placeholder": "権限モードを選択"
|
||||
},
|
||||
"title": "高級設定"
|
||||
},
|
||||
"essential": "必須設定",
|
||||
"prompt": "プロンプト設定"
|
||||
"mcps": "MCPサーバー",
|
||||
"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": "Ferramentas pré-aprovadas",
|
||||
"placeholder": "Selecionar ferramentas pré-aprovadas"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -80,8 +80,37 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"advance": {
|
||||
"maxTurns": {
|
||||
"description": "Define 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"
|
||||
},
|
||||
"permissionMode": {
|
||||
"description": "Controla como o agente 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": "Selecione o 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",
|
||||
"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",
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"allowed_tools": {
|
||||
"empty": "Для этого агента нет доступных инструментов.",
|
||||
"helper": "Выберите инструменты с предварительным допуском. Неотмеченные инструменты потребуют подтверждения перед использованием.",
|
||||
"label": "Разрешённые инструменты",
|
||||
"placeholder": "Выберите разрешённые инструменты"
|
||||
"label": "Предварительно одобренные инструменты",
|
||||
"placeholder": "Выберите предварительно одобренные инструменты"
|
||||
},
|
||||
"create": {
|
||||
"error": {
|
||||
@ -80,8 +80,37 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"advance": {
|
||||
"maxTurns": {
|
||||
"description": "Установить количество циклов запрос/ответ, выполняемых автоматически через прокси.",
|
||||
"helper": "Чем выше значение, тем дольше может работать автономно; чем ниже значение, тем легче контролировать.",
|
||||
"label": "Максимальное количество раундов в сеансе"
|
||||
},
|
||||
"permissionMode": {
|
||||
"description": "Как агент управления обрабатывает ситуации, требующие авторизации.",
|
||||
"label": "Режим разрешений",
|
||||
"options": {
|
||||
"acceptEdits": "Автоматически принимать правки",
|
||||
"bypassPermissions": "Пропустить проверку разрешений",
|
||||
"default": "По умолчанию (спросить перед продолжением)",
|
||||
"plan": "Режим планирования (требуется утверждение плана)"
|
||||
},
|
||||
"placeholder": "Выбрать режим разрешений"
|
||||
},
|
||||
"title": "Расширенные настройки"
|
||||
},
|
||||
"essential": "Основные настройки",
|
||||
"prompt": "Настройки подсказки"
|
||||
"mcps": "MCP сервер",
|
||||
"prompt": "Настройки подсказки",
|
||||
"tools": {
|
||||
"approved": "одобрено",
|
||||
"caution": "Предварительно одобренные инструменты обходят проверку человеком. Включайте только доверенные инструменты.",
|
||||
"description": "Выберите, какие инструменты могут запускаться без ручного подтверждения.",
|
||||
"requiresPermission": "Требуется разрешение, если не предварительно одобрено.",
|
||||
"tab": "Предварительно одобренные инструменты",
|
||||
"title": "Предварительно одобренные инструменты",
|
||||
"toggle": "{{defaultValue}}"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Тип агента",
|
||||
|
||||
@ -0,0 +1,162 @@
|
||||
import { Input, Select, SelectItem, Tooltip } from '@heroui/react'
|
||||
import type { Selection } from '@react-types/shared'
|
||||
import {
|
||||
AgentConfiguration,
|
||||
AgentConfigurationSchema,
|
||||
GetAgentResponse,
|
||||
PermissionMode,
|
||||
PermissionModeSchema,
|
||||
UpdateAgentForm
|
||||
} from '@renderer/types'
|
||||
import { Info } from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
const permissionModeKeyMap: Record<PermissionMode, string> = {
|
||||
default: 'agent.settings.advance.permissionMode.options.default',
|
||||
acceptEdits: 'agent.settings.advance.permissionMode.options.acceptEdits',
|
||||
bypassPermissions: 'agent.settings.advance.permissionMode.options.bypassPermissions',
|
||||
plan: 'agent.settings.advance.permissionMode.options.plan'
|
||||
}
|
||||
|
||||
const permissionModeFallback: Record<PermissionMode, string> = {
|
||||
default: 'Default (ask before continuing)',
|
||||
acceptEdits: 'Accept edits automatically',
|
||||
bypassPermissions: 'Bypass permission checks',
|
||||
plan: 'Planning mode (requires plan approval)'
|
||||
}
|
||||
|
||||
type AgentConfigurationState = AgentConfiguration & Record<string, unknown>
|
||||
|
||||
interface AgentAdvanceSettingsProps {
|
||||
agent: GetAgentResponse | undefined | null
|
||||
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
|
||||
}
|
||||
|
||||
const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState
|
||||
|
||||
export const AgentAdvanceSettings: React.FC<AgentAdvanceSettingsProps> = ({ agent, updateAgent }) => {
|
||||
const { t } = useTranslation()
|
||||
const [configuration, setConfiguration] = useState<AgentConfigurationState>(defaultConfiguration)
|
||||
const [maxTurnsInput, setMaxTurnsInput] = useState<string>(String(defaultConfiguration.max_turns))
|
||||
|
||||
useEffect(() => {
|
||||
if (!agent) {
|
||||
setConfiguration(defaultConfiguration)
|
||||
setMaxTurnsInput(String(defaultConfiguration.max_turns))
|
||||
return
|
||||
}
|
||||
const parsed = AgentConfigurationSchema.parse(agent.configuration ?? {}) as AgentConfigurationState
|
||||
setConfiguration(parsed)
|
||||
setMaxTurnsInput(String(parsed.max_turns))
|
||||
}, [agent])
|
||||
|
||||
const permissionOptions = useMemo(
|
||||
() =>
|
||||
PermissionModeSchema.options.map((mode) => ({
|
||||
key: mode,
|
||||
label: t(permissionModeKeyMap[mode], permissionModeFallback[mode])
|
||||
})) satisfies { key: PermissionMode; label: string }[],
|
||||
[t]
|
||||
)
|
||||
|
||||
const handlePermissionChange = useCallback(
|
||||
(keys: Selection) => {
|
||||
if (!agent || keys === 'all') return
|
||||
const [first] = Array.from(keys)
|
||||
if (!first) return
|
||||
const nextMode = first as PermissionMode
|
||||
setConfiguration((prev) => {
|
||||
if (prev.permission_mode === nextMode) {
|
||||
return prev
|
||||
}
|
||||
const next = { ...prev, permission_mode: nextMode } as AgentConfigurationState
|
||||
updateAgent({ id: agent.id, configuration: next } satisfies UpdateAgentForm)
|
||||
return next
|
||||
})
|
||||
},
|
||||
[agent, updateAgent]
|
||||
)
|
||||
|
||||
const commitMaxTurns = useCallback(() => {
|
||||
if (!agent) return
|
||||
const parsedValue = Number.parseInt(maxTurnsInput, 10)
|
||||
if (!Number.isFinite(parsedValue)) {
|
||||
setMaxTurnsInput(String(configuration.max_turns))
|
||||
return
|
||||
}
|
||||
const sanitized = Math.max(1, parsedValue)
|
||||
if (sanitized === configuration.max_turns) {
|
||||
setMaxTurnsInput(String(configuration.max_turns))
|
||||
return
|
||||
}
|
||||
const next = { ...configuration, max_turns: sanitized } as AgentConfigurationState
|
||||
setConfiguration(next)
|
||||
setMaxTurnsInput(String(sanitized))
|
||||
updateAgent({ id: agent.id, configuration: next } satisfies UpdateAgentForm)
|
||||
}, [agent, configuration, maxTurnsInput, updateAgent])
|
||||
|
||||
if (!agent) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<SettingsItem>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
<Tooltip content={t('agent.settings.advance.permissionMode.description')} placement="right">
|
||||
<Info size={16} className="text-foreground-400" />
|
||||
</Tooltip>
|
||||
}>
|
||||
{t('agent.settings.advance.permissionMode.label')}
|
||||
</SettingsTitle>
|
||||
<Select
|
||||
aria-label={t('agent.settings.advance.permissionMode.label')}
|
||||
selectionMode="single"
|
||||
selectedKeys={[configuration.permission_mode]}
|
||||
onSelectionChange={handlePermissionChange}
|
||||
className="max-w-md"
|
||||
placeholder={t('agent.settings.advance.permissionMode.placeholder')}>
|
||||
{permissionOptions.map((option) => (
|
||||
<SelectItem key={option.key} textValue={option.label}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</SettingsItem>
|
||||
<SettingsItem divider={false}>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
<Tooltip content={t('agent.settings.advance.maxTurns.description')} placement="right">
|
||||
<Info size={16} className="text-foreground-400" />
|
||||
</Tooltip>
|
||||
}>
|
||||
{t('agent.settings.advance.maxTurns.label')}
|
||||
</SettingsTitle>
|
||||
<div className="flex max-w-md flex-col gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={maxTurnsInput}
|
||||
onValueChange={setMaxTurnsInput}
|
||||
onBlur={commitMaxTurns}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
commitMaxTurns()
|
||||
}
|
||||
}}
|
||||
aria-label={t('agent.settings.advance.maxTurns.label')}
|
||||
/>
|
||||
<span className="text-foreground-500 text-xs">
|
||||
{t('agent.settings.advance.maxTurns.helper')}
|
||||
</span>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</SettingsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentAdvanceSettings
|
||||
@ -1,7 +1,5 @@
|
||||
import { Button, Tooltip } from '@heroui/react'
|
||||
import { loggerService } from '@logger'
|
||||
import type { Selection } from '@react-types/shared'
|
||||
import { AllowedToolsSelect } from '@renderer/components/agent'
|
||||
import { ApiModelLabel } from '@renderer/components/ApiModelLabel'
|
||||
import { useApiModels } from '@renderer/hooks/agents/useModels'
|
||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
@ -9,7 +7,7 @@ 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'
|
||||
@ -25,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
|
||||
@ -47,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,
|
||||
@ -62,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
|
||||
|
||||
@ -117,40 +83,6 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
|
||||
[agent, t, updateAccessiblePaths]
|
||||
)
|
||||
|
||||
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
|
||||
})
|
||||
},
|
||||
[agent, availableTools]
|
||||
)
|
||||
|
||||
const onAllowedToolsSelected = useCallback(() => {
|
||||
if (!agent) return
|
||||
const previous = agent.allowed_tools ?? []
|
||||
const previousSet = new Set(previous)
|
||||
const isSameSelection =
|
||||
allowedToolIds.length === previousSet.size && allowedToolIds.every((id) => previousSet.has(id))
|
||||
|
||||
if (isSameSelection) {
|
||||
return
|
||||
}
|
||||
updateAllowedTools(allowedToolIds)
|
||||
}, [agent, allowedToolIds, updateAllowedTools])
|
||||
|
||||
if (!agent) return null
|
||||
|
||||
return (
|
||||
@ -185,15 +117,6 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
|
||||
placeholder={t('common.placeholders.select.model')}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem>
|
||||
<SettingsTitle>{t('agent.session.allowed_tools.label')}</SettingsTitle>
|
||||
<AllowedToolsSelect
|
||||
items={availableTools}
|
||||
selectedKeys={selectedToolKeys}
|
||||
onSelectionChange={onAllowedToolsChange}
|
||||
onClose={onAllowedToolsSelected}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
import { Card, CardBody, CardHeader, Switch, Tooltip } from '@heroui/react'
|
||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||
import { GetAgentResponse, UpdateAgentForm } from '@renderer/types'
|
||||
import { Info } from 'lucide-react'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
interface AgentMCPSettingsProps {
|
||||
agent: GetAgentResponse | undefined | null
|
||||
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
|
||||
}
|
||||
|
||||
export const AgentMCPSettings: React.FC<AgentMCPSettingsProps> = ({ agent, updateAgent }) => {
|
||||
const { t } = useTranslation()
|
||||
const { mcpServers: allMcpServers } = useMCPServers()
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||
|
||||
const availableServers = useMemo(() => allMcpServers ?? [], [allMcpServers])
|
||||
|
||||
useEffect(() => {
|
||||
if (!agent) {
|
||||
setSelectedIds([])
|
||||
return
|
||||
}
|
||||
const mcps = agent.mcps ?? []
|
||||
const validIds = mcps.filter((id) => availableServers.some((server) => server.id === id))
|
||||
setSelectedIds((prev) => {
|
||||
if (prev.length === validIds.length && prev.every((id) => validIds.includes(id))) {
|
||||
return prev
|
||||
}
|
||||
return validIds
|
||||
})
|
||||
}, [agent, availableServers])
|
||||
|
||||
const handleToggle = useCallback(
|
||||
(serverId: string, isEnabled: boolean) => {
|
||||
if (!agent) return
|
||||
|
||||
setSelectedIds((prev) => {
|
||||
const exists = prev.includes(serverId)
|
||||
if (isEnabled === exists) {
|
||||
return prev
|
||||
}
|
||||
const next = isEnabled ? [...prev, serverId] : prev.filter((id) => id !== serverId)
|
||||
updateAgent({ id: agent.id, mcps: next })
|
||||
return next
|
||||
})
|
||||
},
|
||||
[agent, updateAgent]
|
||||
)
|
||||
|
||||
const enabledCount = useMemo(() => {
|
||||
const validSelected = selectedIds.filter((id) => availableServers.some((server) => server.id === id))
|
||||
return validSelected.length
|
||||
}, [selectedIds, availableServers])
|
||||
|
||||
const renderServerMeta = useCallback((meta?: ReactNode) => {
|
||||
if (!meta) return null
|
||||
return <span className="text-foreground-400 text-xs">{meta}</span>
|
||||
}, [])
|
||||
|
||||
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('assistants.settings.mcp.title')}
|
||||
<Tooltip
|
||||
placement="right"
|
||||
content={t('assistants.settings.mcp.description', 'Select MCP servers to use with this agent')}>
|
||||
<Info size={16} className="text-foreground-400" />
|
||||
</Tooltip>
|
||||
</SettingsTitle>
|
||||
{availableServers.length > 0 ? (
|
||||
<span className="text-foreground-500 text-xs">
|
||||
{enabledCount} / {availableServers.length} {t('settings.mcp.active')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{availableServers.length > 0 ? (
|
||||
<div className="flex flex-1 flex-col gap-3 overflow-auto pr-1">
|
||||
{availableServers.map((server) => {
|
||||
const isSelected = selectedIds.includes(server.id)
|
||||
return (
|
||||
<Card key={server.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">{server.name}</span>
|
||||
{server.description ? (
|
||||
<span className="line-clamp-2 text-foreground-500 text-xs">{server.description}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<Switch
|
||||
aria-label={t('assistants.settings.mcp.toggle', {
|
||||
defaultValue: `Toggle ${server.name}`
|
||||
})}
|
||||
isSelected={isSelected}
|
||||
isDisabled={!server.isActive}
|
||||
size="sm"
|
||||
onValueChange={(value) => handleToggle(server.id, value)}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardBody className="gap-1 py-0 pb-3">
|
||||
{renderServerMeta(server.baseUrl)}
|
||||
{renderServerMeta(server.provider)}
|
||||
</CardBody>
|
||||
</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('assistants.settings.mcp.noServersAvailable', 'No MCP servers available')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</SettingsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentMCPSettings
|
||||
@ -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
|
||||
@ -7,8 +7,11 @@ import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AgentAdvanceSettings from './AgentAdvanceSettings'
|
||||
import AgentEssentialSettings from './AgentEssentialSettings'
|
||||
import AgentMCPSettings from './AgentMCPSettings'
|
||||
import AgentPromptSettings from './AgentPromptSettings'
|
||||
import AgentToolSettings from './AgentToolSettings'
|
||||
import { AgentLabel } from './shared'
|
||||
|
||||
interface AgentSettingPopupShowParams {
|
||||
@ -20,7 +23,7 @@ interface AgentSettingPopupParams extends AgentSettingPopupShowParams {
|
||||
resolve: () => void
|
||||
}
|
||||
|
||||
type AgentSettingPopupTab = 'essential' | 'prompt'
|
||||
type AgentSettingPopupTab = 'essential' | 'prompt' | 'tools' | 'mcps' | 'advance' | 'session-mcps'
|
||||
|
||||
const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, agentId, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
@ -51,6 +54,18 @@ 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')
|
||||
},
|
||||
{
|
||||
key: 'advance',
|
||||
label: t('agent.settings.advance.title', 'Advanced Settings')
|
||||
}
|
||||
] as const satisfies { key: AgentSettingPopupTab; label: string }[]
|
||||
).filter(Boolean)
|
||||
@ -81,6 +96,9 @@ 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>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -156,8 +156,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 +168,8 @@ export type BaseAgentForm = {
|
||||
model: string
|
||||
accessible_paths: string[]
|
||||
allowed_tools: string[]
|
||||
mcps?: string[]
|
||||
configuration?: AgentConfiguration
|
||||
}
|
||||
|
||||
export type AddAgentForm = Omit<BaseAgentForm, 'id'> & { id?: never }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user