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:
Vaayne 2025-09-23 11:59:43 +08:00
parent 7ca9dcd2fb
commit 087e757f9f
15 changed files with 1244 additions and 433 deletions

View File

@ -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}}"

View File

@ -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": "预先授权工具",

View File

@ -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": "預先授權工具",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "事前承認済みツール",

View File

@ -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",

View File

@ -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",

View File

@ -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={

View File

@ -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

View File

@ -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

View File

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

View File

@ -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>

View File

@ -45,6 +45,7 @@ export type Tool = z.infer<typeof ToolSchema>
// ------------------ Agent configuration & base schema ------------------
export const AgentConfigurationSchema = z
.object({
// https://docs.claude.com/en/docs/claude-code/sdk/sdk-permissions#mode-specific-behaviors
permission_mode: PermissionModeSchema.default('default'), // Permission mode, default to 'default'
max_turns: z.number().default(10) // Maximum number of interaction turns, default to 10
})