From fb5f109951e9dff25712c9cc4ec7ef0d842c6d13 Mon Sep 17 00:00:00 2001 From: wsp1911 Date: Mon, 29 Jun 2026 13:09:44 +0800 Subject: [PATCH 1/2] feat(agent-tools): tighten tool approvals and shortcuts - Require permissions for Write, Edit, and Delete tool execution - Add a FlowChat approval-bar shortcut to enable tool auto execute - Separate the auto-execute shortcut from per-run approval actions with neutral default styling - Add localized approval-bar copy for auto execute success, failure, label, and tooltip - Allow Claw mode to use WebFetch and simplify Cron tool descriptions - Cover the auto-execute shortcut with focused ToolApprovalBar tests --- .../agentic/agents/definitions/modes/claw.rs | 1 + .../tools/implementations/cron_tool.rs | 4 +- .../tools/implementations/delete_file_tool.rs | 2 +- .../tools/implementations/file_edit_tool.rs | 2 +- .../tools/implementations/file_write_tool.rs | 2 +- .../flow_chat/components/ToolApprovalBar.scss | 22 +++++++ .../components/ToolApprovalBar.test.tsx | 59 +++++++++++++++++++ .../flow_chat/components/ToolApprovalBar.tsx | 42 ++++++++++++- src/web-ui/src/locales/en-US/flow-chat.json | 4 ++ src/web-ui/src/locales/zh-CN/flow-chat.json | 4 ++ src/web-ui/src/locales/zh-TW/flow-chat.json | 4 ++ 11 files changed, 140 insertions(+), 6 deletions(-) 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..dbf782d43 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(--color-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": "拒絕說明", From ecd73c51d3ccbe4d1f05cc59ab761cc7d393995b Mon Sep 17 00:00:00 2001 From: wsp1911 Date: Mon, 29 Jun 2026 15:41:11 +0800 Subject: [PATCH 2/2] fix(logging): suppress noisy third-party parser logs Clamp html5ever and selectors to WARN in desktop and CLI logging so WebFetch readable extraction no longer floods app logs with parser debug output. Move notify from OFF to WARN to keep file-watcher warnings and errors visible while still suppressing lower-level noise. --- src/apps/cli/src/logging.rs | 18 ++++++++++++++++-- src/apps/desktop/src/logging.rs | 4 +++- .../flow_chat/components/ToolApprovalBar.scss | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) 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/web-ui/src/flow_chat/components/ToolApprovalBar.scss b/src/web-ui/src/flow_chat/components/ToolApprovalBar.scss index dbf782d43..a74ed4c35 100644 --- a/src/web-ui/src/flow_chat/components/ToolApprovalBar.scss +++ b/src/web-ui/src/flow_chat/components/ToolApprovalBar.scss @@ -52,7 +52,7 @@ height: 16px; margin: 0 2px; flex-shrink: 0; - background: color-mix(in srgb, var(--color-border-subtle) 80%, transparent); + background: color-mix(in srgb, var(--border-subtle) 80%, transparent); } .tool-approval-bar__auto-execute-button {