diff --git a/src/apps/cli/src/logging.rs b/src/apps/cli/src/logging.rs index bd31f78b1..22fc1136f 100644 --- a/src/apps/cli/src/logging.rs +++ b/src/apps/cli/src/logging.rs @@ -281,11 +281,17 @@ fn target_override_rank(target: &str) -> Option { || matches_target_rule(target, "tracing") || matches_target_rule(target, "opentelemetry_sdk") || matches_target_rule(target, "opentelemetry-otlp") - || matches_target_rule(target, "notify") { return Some(0); } + if matches_target_rule(target, "html5ever") + || matches_target_rule(target, "selectors") + || matches_target_rule(target, "notify") + { + return Some(level_rank(tracing::Level::WARN)); + } + if matches_target_rule(target, "bitfun_core::agentic::events::queue") || matches_target_rule(target, "bitfun_core::agentic::events::router") || matches_target_rule(target, "bitfun_agent_runtime::event_queue") @@ -435,9 +441,17 @@ mod tests { allowed_level_rank_for_target("grep_searcher", tracing::Level::TRACE), level_rank(tracing::Level::WARN) ); + assert_eq!( + allowed_level_rank_for_target("html5ever::tokenizer::char_ref", tracing::Level::TRACE,), + level_rank(tracing::Level::WARN) + ); + assert_eq!( + allowed_level_rank_for_target("selectors::matching", tracing::Level::TRACE), + level_rank(tracing::Level::WARN) + ); assert_eq!( allowed_level_rank_for_target("notify", tracing::Level::TRACE), - 0 + level_rank(tracing::Level::WARN) ); assert_eq!( allowed_level_rank_for_target( diff --git a/src/apps/desktop/src/logging.rs b/src/apps/desktop/src/logging.rs index 65fcc7f51..1e934c155 100644 --- a/src/apps/desktop/src/logging.rs +++ b/src/apps/desktop/src/logging.rs @@ -311,7 +311,9 @@ pub fn build_log_plugin(log_targets: Vec) -> TauriPlugin .level_for("tracing", log::LevelFilter::Off) .level_for("opentelemetry_sdk", log::LevelFilter::Off) .level_for("opentelemetry-otlp", log::LevelFilter::Off) - .level_for("notify", log::LevelFilter::Off) + .level_for("notify", log::LevelFilter::Warn) + .level_for("html5ever", log::LevelFilter::Warn) + .level_for("selectors", log::LevelFilter::Warn) // These targets can emit hot-path trace diagnostics during event // routing. Keep debug diagnostics, warnings, and errors, but avoid // drowning useful app traces in mechanical noise. diff --git a/src/crates/assembly/core/src/agentic/agents/definitions/modes/claw.rs b/src/crates/assembly/core/src/agentic/agents/definitions/modes/claw.rs index 3c3801f39..96203dfcc 100644 --- a/src/crates/assembly/core/src/agentic/agents/definitions/modes/claw.rs +++ b/src/crates/assembly/core/src/agentic/agents/definitions/modes/claw.rs @@ -28,6 +28,7 @@ impl ClawMode { "Grep".to_string(), "Glob".to_string(), "WebSearch".to_string(), + "WebFetch".to_string(), "Skill".to_string(), "Git".to_string(), "SessionControl".to_string(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs index 6cf5ccc3e..4bd23f615 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs @@ -639,7 +639,7 @@ impl Tool for CronTool { } async fn description(&self) -> BitFunResult { - Ok(r#"Manage scheduled jobs for agent sessions. + Ok(r#"Manage scheduled jobs. Defaults: - "session_id": defaults to the current session for "list" and "add". @@ -677,7 +677,7 @@ Patch schema for "update": } fn short_description(&self) -> String { - "Manage scheduled jobs for agent sessions.".to_string() + "Manage scheduled jobs.".to_string() } fn default_exposure(&self) -> ToolExposure { diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/delete_file_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/delete_file_tool.rs index 404b523b1..be73656b6 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/delete_file_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/delete_file_tool.rs @@ -116,7 +116,7 @@ Important notes: } fn needs_permissions(&self, _input: Option<&Value>) -> bool { - false + true } async fn validate_input( diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/file_edit_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/file_edit_tool.rs index b33ee3cae..430992ece 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/file_edit_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/file_edit_tool.rs @@ -158,7 +158,7 @@ impl Tool for FileEditTool { } fn needs_permissions(&self, _input: Option<&Value>) -> bool { - false + true } async fn validate_input( diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs index e05d80b48..fbee0dc82 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs @@ -472,7 +472,7 @@ impl Tool for FileWriteTool { } fn needs_permissions(&self, _input: Option<&Value>) -> bool { - false + true } async fn validate_input( diff --git a/src/web-ui/src/flow_chat/components/ToolApprovalBar.scss b/src/web-ui/src/flow_chat/components/ToolApprovalBar.scss index 1f5c22b7b..a74ed4c35 100644 --- a/src/web-ui/src/flow_chat/components/ToolApprovalBar.scss +++ b/src/web-ui/src/flow_chat/components/ToolApprovalBar.scss @@ -47,6 +47,28 @@ flex-shrink: 0; } +.tool-approval-bar__divider { + width: 1px; + height: 16px; + margin: 0 2px; + flex-shrink: 0; + background: color-mix(in srgb, var(--border-subtle) 80%, transparent); +} + +.tool-approval-bar__auto-execute-button { + color: var(--color-text-secondary); +} + +.tool-approval-bar__auto-execute-button:hover:not(:disabled) { + background: rgba(var(--component-action-success-rgb), 0.18); + color: var(--color-success); +} + +.tool-approval-bar__auto-execute-button:active:not(:disabled) { + background: rgba(var(--component-action-success-rgb), 0.24); + color: var(--color-success); +} + .tool-approval-bar__instruction { display: flex; align-items: center; diff --git a/src/web-ui/src/flow_chat/components/ToolApprovalBar.test.tsx b/src/web-ui/src/flow_chat/components/ToolApprovalBar.test.tsx index f16d7ca10..ec0d264b6 100644 --- a/src/web-ui/src/flow_chat/components/ToolApprovalBar.test.tsx +++ b/src/web-ui/src/flow_chat/components/ToolApprovalBar.test.tsx @@ -14,7 +14,11 @@ const messages: Record = { 'toolCards.approval.confirm': 'Allow', 'toolCards.approval.reject': 'Reject', 'toolCards.approval.rejectWithInstruction': 'Reject with instruction', + 'toolCards.approval.enableAutoExecute': 'Enable tool auto execute', 'toolCards.approval.confirmTooltip': 'Allow this tool run', + 'toolCards.approval.enableAutoExecuteTooltip': '开启工具自动执行', + 'toolCards.approval.autoExecuteEnabled': 'Tool auto execute enabled', + 'toolCards.approval.autoExecuteEnableFailed': 'Failed to enable tool auto execute', 'toolCards.approval.rejectTooltip': 'Reject this tool run', 'toolCards.approval.rejectWithInstructionTooltip': 'Reject and tell the assistant what to do next', 'toolCards.approval.rejectInstructionLabel': 'Rejection instruction', @@ -54,6 +58,36 @@ vi.mock('@/component-library', () => ({ ), })); +const setConfigMock = vi.hoisted(() => vi.fn()); +const notificationSuccessMock = vi.hoisted(() => vi.fn()); +const notificationErrorMock = vi.hoisted(() => vi.fn()); +const eventBusEmitMock = vi.hoisted(() => vi.fn()); + +vi.mock('@/infrastructure/config', () => ({ + configManager: { + setConfig: setConfigMock, + }, +})); + +vi.mock('@/shared/notification-system', () => ({ + notificationService: { + success: notificationSuccessMock, + error: notificationErrorMock, + }, +})); + +vi.mock('@/shared/utils/logger', () => ({ + createLogger: () => ({ + error: vi.fn(), + }), +})); + +vi.mock('@/infrastructure/event-bus', () => ({ + globalEventBus: { + emit: eventBusEmitMock, + }, +})); + function execCommandItem(cmd: string, status: FlowToolItem['status'] = 'pending_confirmation'): FlowToolItem { return { id: 'tool-exec-1', @@ -74,6 +108,11 @@ describe('ToolApprovalBar', () => { let root: Root; beforeEach(() => { + setConfigMock.mockResolvedValue(undefined); + notificationSuccessMock.mockClear(); + notificationErrorMock.mockClear(); + eventBusEmitMock.mockClear(); + dom = new JSDOM('
', { pretendToBeVisual: true, }); @@ -167,6 +206,26 @@ describe('ToolApprovalBar', () => { expect(allowButton.title).toBe('This tool has no executable input'); }); + it('enables tool auto execute from the shared approval bar', async () => { + await act(async () => { + root.render(); + }); + + const autoExecuteButton = container.querySelector( + 'button[aria-label="Enable tool auto execute"]', + ) as HTMLButtonElement; + + expect(autoExecuteButton.title).toBe('开启工具自动执行'); + + await act(async () => { + autoExecuteButton.dispatchEvent(new dom.window.MouseEvent('click', { bubbles: true })); + }); + + expect(setConfigMock).toHaveBeenCalledWith('ai.skip_tool_confirmation', true); + expect(notificationSuccessMock).toHaveBeenCalledWith('Tool auto execute enabled', { duration: 2000 }); + expect(eventBusEmitMock).toHaveBeenCalledWith('mode:config:updated'); + }); + it('does not render for non-confirmation statuses', () => { act(() => { root.render(); diff --git a/src/web-ui/src/flow_chat/components/ToolApprovalBar.tsx b/src/web-ui/src/flow_chat/components/ToolApprovalBar.tsx index fa1b27212..0039efb00 100644 --- a/src/web-ui/src/flow_chat/components/ToolApprovalBar.tsx +++ b/src/web-ui/src/flow_chat/components/ToolApprovalBar.tsx @@ -1,12 +1,17 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { Check, MessageSquareX, X } from 'lucide-react'; +import { Check, MessageSquareX, ShieldCheck, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { IconButton } from '@/component-library'; +import { configManager } from '@/infrastructure/config'; +import { notificationService } from '@/shared/notification-system'; +import { createLogger } from '@/shared/utils/logger'; import { AcpPermissionActions } from '../tool-cards/AcpPermissionActions'; import { hasAcpPermissionOptions } from '../tool-cards/AcpPermissionActions.utils'; import type { FlowToolItem, ToolRejectOptions } from '../types/flow-chat'; import './ToolApprovalBar.scss'; +const log = createLogger('ToolApprovalBar'); + interface ToolApprovalBarProps { toolItem: FlowToolItem; onConfirm?: (updatedInput?: any, permissionOptionId?: string, approve?: boolean) => void; @@ -35,6 +40,7 @@ export const ToolApprovalBar: React.FC = ({ const [nowMs, setNowMs] = useState(() => Date.now()); const [showInstructionInput, setShowInstructionInput] = useState(false); const [instruction, setInstruction] = useState(''); + const [isEnablingAutoExecute, setIsEnablingAutoExecute] = useState(false); const instructionInputRef = useRef(null); const input = toolItem.toolCall?.input; const hasPermissionOptions = hasAcpPermissionOptions(toolItem); @@ -100,6 +106,28 @@ export const ToolApprovalBar: React.FC = ({ onReject?.(); }; + const handleEnableAutoExecute = async (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + if (isEnablingAutoExecute) { + return; + } + + setIsEnablingAutoExecute(true); + try { + await configManager.setConfig('ai.skip_tool_confirmation', true); + notificationService.success(t('toolCards.approval.autoExecuteEnabled'), { duration: 2000 }); + const { globalEventBus } = await import('@/infrastructure/event-bus'); + globalEventBus.emit('mode:config:updated'); + } catch (error) { + log.error('Failed to enable tool auto execute', { error }); + notificationService.error(t('toolCards.approval.autoExecuteEnableFailed')); + } finally { + setIsEnablingAutoExecute(false); + } + }; + const handleRejectWithInstruction = (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); @@ -188,6 +216,18 @@ export const ToolApprovalBar: React.FC = ({ > + diff --git a/src/web-ui/src/locales/en-US/flow-chat.json b/src/web-ui/src/locales/en-US/flow-chat.json index 0c3f6f480..1b643c3cb 100644 --- a/src/web-ui/src/locales/en-US/flow-chat.json +++ b/src/web-ui/src/locales/en-US/flow-chat.json @@ -1361,7 +1361,11 @@ "confirm": "Allow", "reject": "Reject", "rejectWithInstruction": "Reject with instruction", + "enableAutoExecute": "Enable tool auto execute", "confirmTooltip": "Allow this tool run", + "enableAutoExecuteTooltip": "Enable tool auto execute", + "autoExecuteEnabled": "Tool auto execute enabled", + "autoExecuteEnableFailed": "Failed to enable tool auto execute", "rejectTooltip": "Reject this tool run", "rejectWithInstructionTooltip": "Reject and tell the assistant what to do next", "rejectInstructionLabel": "Rejection instruction", diff --git a/src/web-ui/src/locales/zh-CN/flow-chat.json b/src/web-ui/src/locales/zh-CN/flow-chat.json index 1cabedc7f..1cf128e7f 100644 --- a/src/web-ui/src/locales/zh-CN/flow-chat.json +++ b/src/web-ui/src/locales/zh-CN/flow-chat.json @@ -1361,7 +1361,11 @@ "confirm": "允许", "reject": "拒绝", "rejectWithInstruction": "说明后拒绝", + "enableAutoExecute": "开启工具自动执行", "confirmTooltip": "允许执行这个工具", + "enableAutoExecuteTooltip": "开启工具自动执行", + "autoExecuteEnabled": "已开启工具自动执行", + "autoExecuteEnableFailed": "开启工具自动执行失败", "rejectTooltip": "拒绝执行这个工具", "rejectWithInstructionTooltip": "拒绝并告诉助手下一步怎么做", "rejectInstructionLabel": "拒绝说明", diff --git a/src/web-ui/src/locales/zh-TW/flow-chat.json b/src/web-ui/src/locales/zh-TW/flow-chat.json index ebe30bf17..d34c0656d 100644 --- a/src/web-ui/src/locales/zh-TW/flow-chat.json +++ b/src/web-ui/src/locales/zh-TW/flow-chat.json @@ -1361,7 +1361,11 @@ "confirm": "允許", "reject": "拒絕", "rejectWithInstruction": "說明後拒絕", + "enableAutoExecute": "開啟工具自動執行", "confirmTooltip": "允許執行這個工具", + "enableAutoExecuteTooltip": "開啟工具自動執行", + "autoExecuteEnabled": "已開啟工具自動執行", + "autoExecuteEnableFailed": "開啟工具自動執行失敗", "rejectTooltip": "拒絕執行這個工具", "rejectWithInstructionTooltip": "拒絕並告訴助手下一步怎麼做", "rejectInstructionLabel": "拒絕說明",