diff --git a/README.md b/README.md index 3d416f9..e7efa46 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ AI 可以在对话中主动调用本地工具来完成任务,所有操作在 | 🔍 `search_files` | 按文件名/内容搜索(支持扩展名过滤) | 自动 | | 📂 `create_directory` | 创建目录(递归创建) | 自动 | | 🗑️ `delete_file` | 删除文件/目录 | 自动 | -| 💻 `run_command` | 执行 Shell 命令(无超时,通过工作空间终端执行) | 需确认 | +| 💻 `run_command` | 执行 Shell 命令(无超时,通过工作空间终端执行) | 自动/需确认/禁用 | | 📦 `move_file` | 移动/重命名文件或目录 | 自动 | | 📋 `copy_file` | 复制文件或目录 | 自动 | | 🌐 `web_fetch` | 抓取网页内容(HTTP/HTTPS,自动提取文本) | 自动 | @@ -347,7 +347,7 @@ metona-ollama-desktop/ | 温度 | 0.7 | 0=精确,2=创意 | | Think 模式 | 关闭 | 深度推理(需模型支持) | | 工具调用 | 关闭 | AI 主动调用本地工具 | -| 命令执行 | 开启(自动) | Shell 命令执行(高风险) | +| 命令执行 | 需确认 | Shell 命令执行模式:自动/需确认/禁用 | | Agent 记忆 | 开启 | 自动学习+跨会话记忆 | | 工作空间目录 | `userData/workspace` | 命令行默认执行目录 | diff --git a/src/renderer/components/tools-modal.ts b/src/renderer/components/tools-modal.ts index 9b39d49..0eada31 100644 --- a/src/renderer/components/tools-modal.ts +++ b/src/renderer/components/tools-modal.ts @@ -4,7 +4,7 @@ */ import { state, KEYS } from '../state/state.js'; -import { setToolEnabled } from '../services/tool-registry.js'; +import { setToolEnabled, setRunCommandMode } from '../services/tool-registry.js'; import { showToast } from './toast.js'; import { logInfo, logWarn } from '../services/log-service.js'; import type { ChatDB } from '../types.js'; @@ -44,34 +44,47 @@ export function initToolsModal(): void { }); } - // run_command 开关 - const toggle2 = modalEl.querySelector('#toggleRunCommand2') as HTMLInputElement; - if (toggle2) { - toggle2.addEventListener('change', async () => { + // run_command 模式下拉框 + const selectMode = modalEl.querySelector('#selectRunCommandMode') as HTMLSelectElement; + if (selectMode) { + selectMode.addEventListener('change', async () => { const db = state.get(KEYS.DB); - state.set('runCommandEnabled', toggle2.checked); - if (db) await db.saveSetting('runCommandEnabled', toggle2.checked); - setToolEnabled('run_command', toggle2.checked); - if (toggle2.checked) { - showToast('命令执行已开启(每次执行需确认)', 'info'); - logInfo('命令执行已开启'); - } else { - showToast('命令执行已关闭', 'info'); - logInfo('命令执行已关闭'); - } + const mode = selectMode.value as 'auto' | 'confirm' | 'disabled'; + state.set('runCommandMode', mode); + if (db) await db.saveSetting('runCommandMode', mode); + setRunCommandMode(mode); + updateRunCommandBadge(mode); + + const modeNames: Record = { auto: '自动执行', confirm: '需确认', disabled: '已禁用' }; + showToast(`命令执行模式:${modeNames[mode]}`, 'info'); + logInfo(`命令执行模式已切换: ${mode}`); }); } } +function updateRunCommandBadge(mode: string): void { + const badge = modalEl.querySelector('#runCommandBadge') as HTMLElement; + if (!badge) return; + const map: Record = { + auto: { text: '自动', cls: 'auto' }, + confirm: { text: '需确认', cls: 'confirm' }, + disabled: { text: '禁用', cls: 'disabled' } + }; + const info = map[mode] || map.confirm; + badge.textContent = info.text; + badge.className = `tool-card-badge ${info.cls}`; +} + export function openToolsModal(): void { // 同步开关状态 const toolCallingEnabled = state.get('toolCallingEnabled', false); const toggleTC = modalEl.querySelector('#toggleToolCalling') as HTMLInputElement; if (toggleTC) toggleTC.checked = toolCallingEnabled; - const runCmdEnabled = state.get('runCommandEnabled', true); - const toggle2 = modalEl.querySelector('#toggleRunCommand2') as HTMLInputElement; - if (toggle2) toggle2.checked = runCmdEnabled; + const runCommandMode = state.get('runCommandMode', 'confirm'); + const selectMode = modalEl.querySelector('#selectRunCommandMode') as HTMLSelectElement; + if (selectMode) selectMode.value = runCommandMode; + updateRunCommandBadge(runCommandMode); modalEl.style.display = ''; } diff --git a/src/renderer/index.html b/src/renderer/index.html index 6c5d115..49b1a28 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -426,14 +426,15 @@
文件内查找替换,支持全部替换
-
💻run_command需确认
+
💻run_command需确认
执行 shell 命令,无超时限制
- + +
diff --git a/src/renderer/main.ts b/src/renderer/main.ts index 150ef05..aa853eb 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -22,7 +22,7 @@ import { initHistoryModal, closeHistoryModal } from './components/history-modal. import { initMemoryModal } from './components/memory-modal.js'; import { initToolsModal, closeToolsModal } from './components/tools-modal.js'; import { initMemoryManager } from './services/memory-manager.js'; -import { setToolEnabled } from './services/tool-registry.js'; +import { setToolEnabled, setRunCommandMode } from './services/tool-registry.js'; import { initToolConfirmModal } from './components/tool-confirm-modal.js'; import { initWorkspacePanel } from './components/workspace-panel.js'; import { initLogPanel, addLog } from './services/log-service.js'; @@ -190,7 +190,7 @@ async function init(): Promise { // ── 防御性重置:确保首次启动不会遗留脏状态 ── state.set('toolCallingEnabled', false); - state.set('runCommandEnabled', true); // 默认开启(需确认) + state.set('runCommandMode', 'confirm'); // 默认需确认 state.set('memoryEnabled', true); state.set('thinkEnabled', false); @@ -236,14 +236,12 @@ async function init(): Promise { // ── Tool Calling 设置 ── const toolCallingEnabled = await db.getSetting('toolCallingEnabled', false); - const runCommandEnabled = await db.getSetting('runCommandEnabled', true); // 默认开启(需确认) + const runCommandMode = await db.getSetting('runCommandMode', 'confirm') as 'auto' | 'confirm' | 'disabled'; state.set('toolCallingEnabled', toolCallingEnabled); - state.set('runCommandEnabled', runCommandEnabled); - if (!runCommandEnabled) setToolEnabled('run_command', false); // 仅在用户明确关闭时禁用 + state.set('runCommandMode', runCommandMode); + setRunCommandMode(runCommandMode); const toggleTC = document.querySelector('#toggleToolCalling'); if (toggleTC) toggleTC.checked = toolCallingEnabled; - const toggleRC = document.querySelector('#toggleRunCommand'); - if (toggleRC) toggleRC.checked = runCommandEnabled; // ── Agent 记忆设置 ── const memoryEnabled = await db.getSetting('memoryEnabled', true); diff --git a/src/renderer/services/tool-registry.ts b/src/renderer/services/tool-registry.ts index 4893f69..64b4f41 100644 --- a/src/renderer/services/tool-registry.ts +++ b/src/renderer/services/tool-registry.ts @@ -356,9 +356,24 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [ const CONFIRM_TOOLS = ['run_command']; export function needsConfirmation(toolName: string): boolean { + if (toolName === 'run_command') { + // run_command 确认模式由 runCommandMode 状态控制 + const runCommandMode = (window as any).__runCommandMode || 'confirm'; + return runCommandMode === 'confirm'; + } return CONFIRM_TOOLS.includes(toolName); } +/** 设置 run_command 执行模式 */ +export function setRunCommandMode(mode: 'auto' | 'confirm' | 'disabled'): void { + (window as any).__runCommandMode = mode; + if (mode === 'disabled') { + setToolEnabled('run_command', false); + } else { + setToolEnabled('run_command', true); + } +} + let enabledTools: Set = new Set([ 'read_file', 'list_directory', 'search_files', 'write_file', 'create_directory', 'delete_file', diff --git a/src/renderer/styles/style.css b/src/renderer/styles/style.css index f11ece9..c61c8c5 100644 --- a/src/renderer/styles/style.css +++ b/src/renderer/styles/style.css @@ -2937,6 +2937,12 @@ html, body { border: 1px solid rgba(252, 225, 0, 0.15); } +.tool-card-badge.disabled { + color: var(--critical); + background: var(--critical-bg); + border: 1px solid rgba(241, 112, 123, 0.15); +} + .tool-card-desc { font-size: 12px; color: var(--text-secondary); diff --git a/src/renderer/types.d.ts b/src/renderer/types.d.ts index 898b0a5..df525a6 100644 --- a/src/renderer/types.d.ts +++ b/src/renderer/types.d.ts @@ -274,7 +274,7 @@ export type StateKey = | 'temperature' | 'thinkEnabled' | 'toolCallingEnabled' - | 'runCommandEnabled' + | 'runCommandMode' | 'memoryEnabled' | 'memoryEntries' | 'embeddingModel';