mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 23:12:38 +08:00
feat: Refactor agent settings and introduce tooling permissions
- Translated and reorganized Russian language JSON for tooling and permissions. - Removed deprecated MCP and tool settings components. - Introduced new AgentToolingSettings component to manage tooling permissions and MCP servers. - Updated AgentSettings index to reflect new tooling settings structure. - Enhanced agent configuration schema to include permission modes with default values.
This commit is contained in:
parent
7ca9dcd2fb
commit
087e757f9f
@ -80,33 +80,102 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
"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)"
|
||||
},
|
||||
"essential": "Essential 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.",
|
||||
"placeholder": "Choose a permission behavior"
|
||||
},
|
||||
"title": "Advanced Settings"
|
||||
},
|
||||
"essential": "Essential 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}}"
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "高级设置"
|
||||
},
|
||||
"essential": "基础设置",
|
||||
"mcps": "MCP 服务器",
|
||||
"prompt": "提示词设置",
|
||||
"tools": {
|
||||
"approved": "已授权",
|
||||
"caution": "预先授权的工具会跳过人工审核,请仅启用可信的工具。",
|
||||
"essential": "基础设置",
|
||||
"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": "预先授权工具",
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "進階設定"
|
||||
},
|
||||
"essential": "必要設定",
|
||||
"mcps": "MCP 伺服器",
|
||||
"prompt": "提示設定",
|
||||
"tools": {
|
||||
"approved": "已授權",
|
||||
"caution": "預先授權的工具會略過人工審查,請僅啟用可信任的工具。",
|
||||
"essential": "必要設定",
|
||||
"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": "預先授權工具",
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "[to be translated]:高级设置"
|
||||
},
|
||||
"essential": "Βασικές Ρυθμίσεις",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "Ρυθμίσεις Προτροπής",
|
||||
"tools": {
|
||||
"approved": "[to be translated]:approved",
|
||||
"caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.",
|
||||
"essential": "Βασικές Ρυθμίσεις",
|
||||
"prompt": "Ρυθμίσεις Προτροπής",
|
||||
"tooling": {
|
||||
"mcp": {
|
||||
"description": "[to be translated]:Connect MCP servers to unlock additional tools you can approve above.",
|
||||
"empty": "[to be translated]:No MCP servers detected. Add one from the MCP settings page.",
|
||||
"manageHint": "[to be translated]:Need advanced configuration? Visit Settings → MCP Servers.",
|
||||
"toggle": "[to be translated]:Toggle {{name}}"
|
||||
},
|
||||
"permissionMode": {
|
||||
"acceptEdits": {
|
||||
"behavior": "[to be translated]:Pre-approves trusted filesystem tools so edits run immediately.",
|
||||
"description": "[to be translated]:File edits and filesystem operations are automatically approved.",
|
||||
"title": "[to be translated]:Auto-accept file edits"
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"behavior": "[to be translated]:Every tool is pre-approved automatically.",
|
||||
"description": "[to be translated]:All permission prompts are skipped — use with caution.",
|
||||
"title": "[to be translated]:Bypass permission checks",
|
||||
"warning": "[to be translated]:Use with caution — all tools will run without asking for approval."
|
||||
},
|
||||
"confirmChange": {
|
||||
"description": "[to be translated]:Switching modes updates the automatically approved tools.",
|
||||
"title": "[to be translated]:Change permission mode?"
|
||||
},
|
||||
"default": {
|
||||
"behavior": "[to be translated]:No tools are pre-approved automatically.",
|
||||
"description": "[to be translated]:Normal permission checks apply.",
|
||||
"title": "[to be translated]:Default (ask before continuing)"
|
||||
},
|
||||
"plan": {
|
||||
"behavior": "[to be translated]:Read-only tools only. Execution is disabled.",
|
||||
"description": "[to be translated]:Claude can only use read-only tools and presents a plan before execution.",
|
||||
"title": "[to be translated]:Planning mode (coming soon)"
|
||||
}
|
||||
},
|
||||
"preapproved": {
|
||||
"autoBadge": "[to be translated]:Added by mode",
|
||||
"autoDescription": "[to be translated]:This tool is auto-approved by the current permission mode.",
|
||||
"empty": "[to be translated]:No tools match your filters.",
|
||||
"mcpBadge": "[to be translated]:MCP tool",
|
||||
"requiresApproval": "[to be translated]:Requires approval when disabled",
|
||||
"search": "[to be translated]:Search tools",
|
||||
"toggle": "[to be translated]:Toggle {{name}}",
|
||||
"warning": {
|
||||
"description": "[to be translated]:Enable only tools you trust. Mode defaults are highlighted automatically.",
|
||||
"title": "[to be translated]:Pre-approved tools run without manual review."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"autoTools": "[to be translated]:Auto: {{count}}",
|
||||
"customTools": "[to be translated]:Custom: {{count}}",
|
||||
"helper": "[to be translated]:Changes save automatically. Adjust the steps above any time to fine-tune permissions.",
|
||||
"mcp": "[to be translated]:MCP: {{count}}",
|
||||
"mode": "[to be translated]:Mode: {{mode}}"
|
||||
},
|
||||
"steps": {
|
||||
"mcp": {
|
||||
"title": "[to be translated]:MCP servers"
|
||||
},
|
||||
"permissionMode": {
|
||||
"title": "[to be translated]:Step 1 · Permission mode"
|
||||
},
|
||||
"preapproved": {
|
||||
"title": "[to be translated]:Step 2 · Pre-approved tools"
|
||||
},
|
||||
"review": {
|
||||
"title": "[to be translated]:Step 3 · Review"
|
||||
}
|
||||
},
|
||||
"tab": "[to be translated]:Tooling & permissions"
|
||||
},
|
||||
"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",
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "[to be translated]:高级设置"
|
||||
},
|
||||
"essential": "Configuraciones esenciales",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"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.",
|
||||
"essential": "Configuraciones esenciales",
|
||||
"prompt": "Configuración de indicaciones",
|
||||
"tooling": {
|
||||
"mcp": {
|
||||
"description": "[to be translated]:Connect MCP servers to unlock additional tools you can approve above.",
|
||||
"empty": "[to be translated]:No MCP servers detected. Add one from the MCP settings page.",
|
||||
"manageHint": "[to be translated]:Need advanced configuration? Visit Settings → MCP Servers.",
|
||||
"toggle": "[to be translated]:Toggle {{name}}"
|
||||
},
|
||||
"permissionMode": {
|
||||
"acceptEdits": {
|
||||
"behavior": "[to be translated]:Pre-approves trusted filesystem tools so edits run immediately.",
|
||||
"description": "[to be translated]:File edits and filesystem operations are automatically approved.",
|
||||
"title": "[to be translated]:Auto-accept file edits"
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"behavior": "[to be translated]:Every tool is pre-approved automatically.",
|
||||
"description": "[to be translated]:All permission prompts are skipped — use with caution.",
|
||||
"title": "[to be translated]:Bypass permission checks",
|
||||
"warning": "[to be translated]:Use with caution — all tools will run without asking for approval."
|
||||
},
|
||||
"confirmChange": {
|
||||
"description": "[to be translated]:Switching modes updates the automatically approved tools.",
|
||||
"title": "[to be translated]:Change permission mode?"
|
||||
},
|
||||
"default": {
|
||||
"behavior": "[to be translated]:No tools are pre-approved automatically.",
|
||||
"description": "[to be translated]:Normal permission checks apply.",
|
||||
"title": "[to be translated]:Default (ask before continuing)"
|
||||
},
|
||||
"plan": {
|
||||
"behavior": "[to be translated]:Read-only tools only. Execution is disabled.",
|
||||
"description": "[to be translated]:Claude can only use read-only tools and presents a plan before execution.",
|
||||
"title": "[to be translated]:Planning mode (coming soon)"
|
||||
}
|
||||
},
|
||||
"preapproved": {
|
||||
"autoBadge": "[to be translated]:Added by mode",
|
||||
"autoDescription": "[to be translated]:This tool is auto-approved by the current permission mode.",
|
||||
"empty": "[to be translated]:No tools match your filters.",
|
||||
"mcpBadge": "[to be translated]:MCP tool",
|
||||
"requiresApproval": "[to be translated]:Requires approval when disabled",
|
||||
"search": "[to be translated]:Search tools",
|
||||
"toggle": "[to be translated]:Toggle {{name}}",
|
||||
"warning": {
|
||||
"description": "[to be translated]:Enable only tools you trust. Mode defaults are highlighted automatically.",
|
||||
"title": "[to be translated]:Pre-approved tools run without manual review."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"autoTools": "[to be translated]:Auto: {{count}}",
|
||||
"customTools": "[to be translated]:Custom: {{count}}",
|
||||
"helper": "[to be translated]:Changes save automatically. Adjust the steps above any time to fine-tune permissions.",
|
||||
"mcp": "[to be translated]:MCP: {{count}}",
|
||||
"mode": "[to be translated]:Mode: {{mode}}"
|
||||
},
|
||||
"steps": {
|
||||
"mcp": {
|
||||
"title": "[to be translated]:MCP servers"
|
||||
},
|
||||
"permissionMode": {
|
||||
"title": "[to be translated]:Step 1 · Permission mode"
|
||||
},
|
||||
"preapproved": {
|
||||
"title": "[to be translated]:Step 2 · Pre-approved tools"
|
||||
},
|
||||
"review": {
|
||||
"title": "[to be translated]:Step 3 · Review"
|
||||
}
|
||||
},
|
||||
"tab": "[to be translated]:Tooling & permissions"
|
||||
},
|
||||
"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",
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "[to be translated]:高级设置"
|
||||
},
|
||||
"essential": "Paramètres essentiels",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"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.",
|
||||
"essential": "Paramètres essentiels",
|
||||
"prompt": "Paramètres de l'invite",
|
||||
"tooling": {
|
||||
"mcp": {
|
||||
"description": "[to be translated]:Connect MCP servers to unlock additional tools you can approve above.",
|
||||
"empty": "[to be translated]:No MCP servers detected. Add one from the MCP settings page.",
|
||||
"manageHint": "[to be translated]:Need advanced configuration? Visit Settings → MCP Servers.",
|
||||
"toggle": "[to be translated]:Toggle {{name}}"
|
||||
},
|
||||
"permissionMode": {
|
||||
"acceptEdits": {
|
||||
"behavior": "[to be translated]:Pre-approves trusted filesystem tools so edits run immediately.",
|
||||
"description": "[to be translated]:File edits and filesystem operations are automatically approved.",
|
||||
"title": "[to be translated]:Auto-accept file edits"
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"behavior": "[to be translated]:Every tool is pre-approved automatically.",
|
||||
"description": "[to be translated]:All permission prompts are skipped — use with caution.",
|
||||
"title": "[to be translated]:Bypass permission checks",
|
||||
"warning": "[to be translated]:Use with caution — all tools will run without asking for approval."
|
||||
},
|
||||
"confirmChange": {
|
||||
"description": "[to be translated]:Switching modes updates the automatically approved tools.",
|
||||
"title": "[to be translated]:Change permission mode?"
|
||||
},
|
||||
"default": {
|
||||
"behavior": "[to be translated]:No tools are pre-approved automatically.",
|
||||
"description": "[to be translated]:Normal permission checks apply.",
|
||||
"title": "[to be translated]:Default (ask before continuing)"
|
||||
},
|
||||
"plan": {
|
||||
"behavior": "[to be translated]:Read-only tools only. Execution is disabled.",
|
||||
"description": "[to be translated]:Claude can only use read-only tools and presents a plan before execution.",
|
||||
"title": "[to be translated]:Planning mode (coming soon)"
|
||||
}
|
||||
},
|
||||
"preapproved": {
|
||||
"autoBadge": "[to be translated]:Added by mode",
|
||||
"autoDescription": "[to be translated]:This tool is auto-approved by the current permission mode.",
|
||||
"empty": "[to be translated]:No tools match your filters.",
|
||||
"mcpBadge": "[to be translated]:MCP tool",
|
||||
"requiresApproval": "[to be translated]:Requires approval when disabled",
|
||||
"search": "[to be translated]:Search tools",
|
||||
"toggle": "[to be translated]:Toggle {{name}}",
|
||||
"warning": {
|
||||
"description": "[to be translated]:Enable only tools you trust. Mode defaults are highlighted automatically.",
|
||||
"title": "[to be translated]:Pre-approved tools run without manual review."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"autoTools": "[to be translated]:Auto: {{count}}",
|
||||
"customTools": "[to be translated]:Custom: {{count}}",
|
||||
"helper": "[to be translated]:Changes save automatically. Adjust the steps above any time to fine-tune permissions.",
|
||||
"mcp": "[to be translated]:MCP: {{count}}",
|
||||
"mode": "[to be translated]:Mode: {{mode}}"
|
||||
},
|
||||
"steps": {
|
||||
"mcp": {
|
||||
"title": "[to be translated]:MCP servers"
|
||||
},
|
||||
"permissionMode": {
|
||||
"title": "[to be translated]:Step 1 · Permission mode"
|
||||
},
|
||||
"preapproved": {
|
||||
"title": "[to be translated]:Step 2 · Pre-approved tools"
|
||||
},
|
||||
"review": {
|
||||
"title": "[to be translated]:Step 3 · Review"
|
||||
}
|
||||
},
|
||||
"tab": "[to be translated]:Tooling & permissions"
|
||||
},
|
||||
"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",
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "[to be translated]:高级设置"
|
||||
},
|
||||
"essential": "必須設定",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "プロンプト設定",
|
||||
"tools": {
|
||||
"approved": "承認済み",
|
||||
"caution": "事前承認したツールは人によるレビューをスキップします。信頼できるツールのみ有効にしてください。",
|
||||
"essential": "必須設定",
|
||||
"prompt": "プロンプト設定",
|
||||
"tooling": {
|
||||
"mcp": {
|
||||
"description": "[to be translated]:Connect MCP servers to unlock additional tools you can approve above.",
|
||||
"empty": "[to be translated]:No MCP servers detected. Add one from the MCP settings page.",
|
||||
"manageHint": "[to be translated]:Need advanced configuration? Visit Settings → MCP Servers.",
|
||||
"toggle": "[to be translated]:Toggle {{name}}"
|
||||
},
|
||||
"permissionMode": {
|
||||
"acceptEdits": {
|
||||
"behavior": "[to be translated]:Pre-approves trusted filesystem tools so edits run immediately.",
|
||||
"description": "[to be translated]:File edits and filesystem operations are automatically approved.",
|
||||
"title": "[to be translated]:Auto-accept file edits"
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"behavior": "[to be translated]:Every tool is pre-approved automatically.",
|
||||
"description": "[to be translated]:All permission prompts are skipped — use with caution.",
|
||||
"title": "[to be translated]:Bypass permission checks",
|
||||
"warning": "[to be translated]:Use with caution — all tools will run without asking for approval."
|
||||
},
|
||||
"confirmChange": {
|
||||
"description": "[to be translated]:Switching modes updates the automatically approved tools.",
|
||||
"title": "[to be translated]:Change permission mode?"
|
||||
},
|
||||
"default": {
|
||||
"behavior": "[to be translated]:No tools are pre-approved automatically.",
|
||||
"description": "[to be translated]:Normal permission checks apply.",
|
||||
"title": "[to be translated]:Default (ask before continuing)"
|
||||
},
|
||||
"plan": {
|
||||
"behavior": "[to be translated]:Read-only tools only. Execution is disabled.",
|
||||
"description": "[to be translated]:Claude can only use read-only tools and presents a plan before execution.",
|
||||
"title": "[to be translated]:Planning mode (coming soon)"
|
||||
}
|
||||
},
|
||||
"preapproved": {
|
||||
"autoBadge": "[to be translated]:Added by mode",
|
||||
"autoDescription": "[to be translated]:This tool is auto-approved by the current permission mode.",
|
||||
"empty": "[to be translated]:No tools match your filters.",
|
||||
"mcpBadge": "[to be translated]:MCP tool",
|
||||
"requiresApproval": "[to be translated]:Requires approval when disabled",
|
||||
"search": "[to be translated]:Search tools",
|
||||
"toggle": "[to be translated]:Toggle {{name}}",
|
||||
"warning": {
|
||||
"description": "[to be translated]:Enable only tools you trust. Mode defaults are highlighted automatically.",
|
||||
"title": "[to be translated]:Pre-approved tools run without manual review."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"autoTools": "[to be translated]:Auto: {{count}}",
|
||||
"customTools": "[to be translated]:Custom: {{count}}",
|
||||
"helper": "[to be translated]:Changes save automatically. Adjust the steps above any time to fine-tune permissions.",
|
||||
"mcp": "[to be translated]:MCP: {{count}}",
|
||||
"mode": "[to be translated]:Mode: {{mode}}"
|
||||
},
|
||||
"steps": {
|
||||
"mcp": {
|
||||
"title": "[to be translated]:MCP servers"
|
||||
},
|
||||
"permissionMode": {
|
||||
"title": "[to be translated]:Step 1 · Permission mode"
|
||||
},
|
||||
"preapproved": {
|
||||
"title": "[to be translated]:Step 2 · Pre-approved tools"
|
||||
},
|
||||
"review": {
|
||||
"title": "[to be translated]:Step 3 · Review"
|
||||
}
|
||||
},
|
||||
"tab": "[to be translated]:Tooling & permissions"
|
||||
},
|
||||
"tools": {
|
||||
"approved": "承認済み",
|
||||
"caution": "事前承認したツールは人によるレビューをスキップします。信頼できるツールのみ有効にしてください。",
|
||||
"description": "人による承認なしで実行できるツールを選択します。",
|
||||
"requiresPermission": "事前承認されていない場合は承認が必要です。",
|
||||
"tab": "事前承認済みツール",
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "[to be translated]:高级设置"
|
||||
},
|
||||
"essential": "Configurações Essenciais",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"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.",
|
||||
"essential": "Configurações Essenciais",
|
||||
"prompt": "Configurações de Prompt",
|
||||
"tooling": {
|
||||
"mcp": {
|
||||
"description": "[to be translated]:Connect MCP servers to unlock additional tools you can approve above.",
|
||||
"empty": "[to be translated]:No MCP servers detected. Add one from the MCP settings page.",
|
||||
"manageHint": "[to be translated]:Need advanced configuration? Visit Settings → MCP Servers.",
|
||||
"toggle": "[to be translated]:Toggle {{name}}"
|
||||
},
|
||||
"permissionMode": {
|
||||
"acceptEdits": {
|
||||
"behavior": "[to be translated]:Pre-approves trusted filesystem tools so edits run immediately.",
|
||||
"description": "[to be translated]:File edits and filesystem operations are automatically approved.",
|
||||
"title": "[to be translated]:Auto-accept file edits"
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"behavior": "[to be translated]:Every tool is pre-approved automatically.",
|
||||
"description": "[to be translated]:All permission prompts are skipped — use with caution.",
|
||||
"title": "[to be translated]:Bypass permission checks",
|
||||
"warning": "[to be translated]:Use with caution — all tools will run without asking for approval."
|
||||
},
|
||||
"confirmChange": {
|
||||
"description": "[to be translated]:Switching modes updates the automatically approved tools.",
|
||||
"title": "[to be translated]:Change permission mode?"
|
||||
},
|
||||
"default": {
|
||||
"behavior": "[to be translated]:No tools are pre-approved automatically.",
|
||||
"description": "[to be translated]:Normal permission checks apply.",
|
||||
"title": "[to be translated]:Default (ask before continuing)"
|
||||
},
|
||||
"plan": {
|
||||
"behavior": "[to be translated]:Read-only tools only. Execution is disabled.",
|
||||
"description": "[to be translated]:Claude can only use read-only tools and presents a plan before execution.",
|
||||
"title": "[to be translated]:Planning mode (coming soon)"
|
||||
}
|
||||
},
|
||||
"preapproved": {
|
||||
"autoBadge": "[to be translated]:Added by mode",
|
||||
"autoDescription": "[to be translated]:This tool is auto-approved by the current permission mode.",
|
||||
"empty": "[to be translated]:No tools match your filters.",
|
||||
"mcpBadge": "[to be translated]:MCP tool",
|
||||
"requiresApproval": "[to be translated]:Requires approval when disabled",
|
||||
"search": "[to be translated]:Search tools",
|
||||
"toggle": "[to be translated]:Toggle {{name}}",
|
||||
"warning": {
|
||||
"description": "[to be translated]:Enable only tools you trust. Mode defaults are highlighted automatically.",
|
||||
"title": "[to be translated]:Pre-approved tools run without manual review."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"autoTools": "[to be translated]:Auto: {{count}}",
|
||||
"customTools": "[to be translated]:Custom: {{count}}",
|
||||
"helper": "[to be translated]:Changes save automatically. Adjust the steps above any time to fine-tune permissions.",
|
||||
"mcp": "[to be translated]:MCP: {{count}}",
|
||||
"mode": "[to be translated]:Mode: {{mode}}"
|
||||
},
|
||||
"steps": {
|
||||
"mcp": {
|
||||
"title": "[to be translated]:MCP servers"
|
||||
},
|
||||
"permissionMode": {
|
||||
"title": "[to be translated]:Step 1 · Permission mode"
|
||||
},
|
||||
"preapproved": {
|
||||
"title": "[to be translated]:Step 2 · Pre-approved tools"
|
||||
},
|
||||
"review": {
|
||||
"title": "[to be translated]:Step 3 · Review"
|
||||
}
|
||||
},
|
||||
"tab": "[to be translated]:Tooling & permissions"
|
||||
},
|
||||
"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",
|
||||
|
||||
@ -99,12 +99,81 @@
|
||||
},
|
||||
"title": "[to be translated]:高级设置"
|
||||
},
|
||||
"essential": "Основные настройки",
|
||||
"mcps": "[to be translated]:MCP 服务器",
|
||||
"prompt": "Настройки подсказки",
|
||||
"tools": {
|
||||
"approved": "[to be translated]:approved",
|
||||
"caution": "[to be translated]:Pre-approved tools bypass human review. Enable only trusted tools.",
|
||||
"essential": "Основные настройки",
|
||||
"prompt": "Настройки подсказки",
|
||||
"tooling": {
|
||||
"mcp": {
|
||||
"description": "[to be translated]:Connect MCP servers to unlock additional tools you can approve above.",
|
||||
"empty": "[to be translated]:No MCP servers detected. Add one from the MCP settings page.",
|
||||
"manageHint": "[to be translated]:Need advanced configuration? Visit Settings → MCP Servers.",
|
||||
"toggle": "[to be translated]:Toggle {{name}}"
|
||||
},
|
||||
"permissionMode": {
|
||||
"acceptEdits": {
|
||||
"behavior": "[to be translated]:Pre-approves trusted filesystem tools so edits run immediately.",
|
||||
"description": "[to be translated]:File edits and filesystem operations are automatically approved.",
|
||||
"title": "[to be translated]:Auto-accept file edits"
|
||||
},
|
||||
"bypassPermissions": {
|
||||
"behavior": "[to be translated]:Every tool is pre-approved automatically.",
|
||||
"description": "[to be translated]:All permission prompts are skipped — use with caution.",
|
||||
"title": "[to be translated]:Bypass permission checks",
|
||||
"warning": "[to be translated]:Use with caution — all tools will run without asking for approval."
|
||||
},
|
||||
"confirmChange": {
|
||||
"description": "[to be translated]:Switching modes updates the automatically approved tools.",
|
||||
"title": "[to be translated]:Change permission mode?"
|
||||
},
|
||||
"default": {
|
||||
"behavior": "[to be translated]:No tools are pre-approved automatically.",
|
||||
"description": "[to be translated]:Normal permission checks apply.",
|
||||
"title": "[to be translated]:Default (ask before continuing)"
|
||||
},
|
||||
"plan": {
|
||||
"behavior": "[to be translated]:Read-only tools only. Execution is disabled.",
|
||||
"description": "[to be translated]:Claude can only use read-only tools and presents a plan before execution.",
|
||||
"title": "[to be translated]:Planning mode (coming soon)"
|
||||
}
|
||||
},
|
||||
"preapproved": {
|
||||
"autoBadge": "[to be translated]:Added by mode",
|
||||
"autoDescription": "[to be translated]:This tool is auto-approved by the current permission mode.",
|
||||
"empty": "[to be translated]:No tools match your filters.",
|
||||
"mcpBadge": "[to be translated]:MCP tool",
|
||||
"requiresApproval": "[to be translated]:Requires approval when disabled",
|
||||
"search": "[to be translated]:Search tools",
|
||||
"toggle": "[to be translated]:Toggle {{name}}",
|
||||
"warning": {
|
||||
"description": "[to be translated]:Enable only tools you trust. Mode defaults are highlighted automatically.",
|
||||
"title": "[to be translated]:Pre-approved tools run without manual review."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"autoTools": "[to be translated]:Auto: {{count}}",
|
||||
"customTools": "[to be translated]:Custom: {{count}}",
|
||||
"helper": "[to be translated]:Changes save automatically. Adjust the steps above any time to fine-tune permissions.",
|
||||
"mcp": "[to be translated]:MCP: {{count}}",
|
||||
"mode": "[to be translated]:Mode: {{mode}}"
|
||||
},
|
||||
"steps": {
|
||||
"mcp": {
|
||||
"title": "[to be translated]:MCP servers"
|
||||
},
|
||||
"permissionMode": {
|
||||
"title": "[to be translated]:Step 1 · Permission mode"
|
||||
},
|
||||
"preapproved": {
|
||||
"title": "[to be translated]:Step 2 · Pre-approved tools"
|
||||
},
|
||||
"review": {
|
||||
"title": "[to be translated]:Step 3 · Review"
|
||||
}
|
||||
},
|
||||
"tab": "[to be translated]:Tooling & permissions"
|
||||
},
|
||||
"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",
|
||||
|
||||
@ -1,33 +1,16 @@
|
||||
import { Input, Select, SelectItem, Tooltip } from '@heroui/react'
|
||||
import type { Selection } from '@react-types/shared'
|
||||
import { Input, Tooltip } from '@heroui/react'
|
||||
import {
|
||||
AgentConfiguration,
|
||||
AgentConfigurationSchema,
|
||||
GetAgentResponse,
|
||||
PermissionMode,
|
||||
PermissionModeSchema,
|
||||
UpdateAgentForm
|
||||
} from '@renderer/types'
|
||||
import { Info } from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, 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 {
|
||||
@ -53,33 +36,6 @@ export const AgentAdvanceSettings: React.FC<AgentAdvanceSettingsProps> = ({ agen
|
||||
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)
|
||||
@ -104,29 +60,6 @@ export const AgentAdvanceSettings: React.FC<AgentAdvanceSettingsProps> = ({ agen
|
||||
|
||||
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={
|
||||
|
||||
@ -1,130 +0,0 @@
|
||||
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
|
||||
@ -1,149 +0,0 @@
|
||||
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
|
||||
@ -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
|
||||
@ -9,9 +9,8 @@ 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 AgentToolingSettings from './AgentToolingSettings'
|
||||
import { AgentLabel } from './shared'
|
||||
|
||||
interface AgentSettingPopupShowParams {
|
||||
@ -23,7 +22,7 @@ interface AgentSettingPopupParams extends AgentSettingPopupShowParams {
|
||||
resolve: () => void
|
||||
}
|
||||
|
||||
type AgentSettingPopupTab = 'essential' | 'prompt' | 'tools' | 'mcps' | 'advance' | 'session-mcps'
|
||||
type AgentSettingPopupTab = 'essential' | 'prompt' | 'tooling' | 'advance' | 'session-mcps'
|
||||
|
||||
const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, agentId, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
@ -56,12 +55,8 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
|
||||
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: 'tooling',
|
||||
label: t('agent.settings.tooling.tab', 'Tooling & permissions')
|
||||
},
|
||||
{
|
||||
key: 'advance',
|
||||
@ -96,8 +91,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 === 'tooling' && <AgentToolingSettings agent={agent} updateAgent={updateAgent} />}
|
||||
{menu === 'advance' && <AgentAdvanceSettings agent={agent} updateAgent={updateAgent} />}
|
||||
</Settings>
|
||||
</div>
|
||||
|
||||
@ -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
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user