Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/apps/cli/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,17 @@ fn target_override_rank(target: &str) -> Option<u8> {
|| 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")
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 3 additions & 1 deletion src/apps/desktop/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,9 @@ pub fn build_log_plugin<R: Runtime>(log_targets: Vec<Target>) -> TauriPlugin<R>
.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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ impl Tool for CronTool {
}

async fn description(&self) -> BitFunResult<String> {
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".
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Important notes:
}

fn needs_permissions(&self, _input: Option<&Value>) -> bool {
false
true
}

async fn validate_input(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl Tool for FileEditTool {
}

fn needs_permissions(&self, _input: Option<&Value>) -> bool {
false
true
}

async fn validate_input(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ impl Tool for FileWriteTool {
}

fn needs_permissions(&self, _input: Option<&Value>) -> bool {
false
true
}

async fn validate_input(
Expand Down
22 changes: 22 additions & 0 deletions src/web-ui/src/flow_chat/components/ToolApprovalBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
59 changes: 59 additions & 0 deletions src/web-ui/src/flow_chat/components/ToolApprovalBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ const messages: Record<string, string> = {
'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',
Expand Down Expand Up @@ -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',
Expand All @@ -74,6 +108,11 @@ describe('ToolApprovalBar', () => {
let root: Root;

beforeEach(() => {
setConfigMock.mockResolvedValue(undefined);
notificationSuccessMock.mockClear();
notificationErrorMock.mockClear();
eventBusEmitMock.mockClear();

dom = new JSDOM('<!doctype html><html><body><div id="root"></div></body></html>', {
pretendToBeVisual: true,
});
Expand Down Expand Up @@ -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(<ToolApprovalBar toolItem={execCommandItem('npm test')} />);
});

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(<ToolApprovalBar toolItem={execCommandItem('npm test', 'running')} />);
Expand Down
42 changes: 41 additions & 1 deletion src/web-ui/src/flow_chat/components/ToolApprovalBar.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -35,6 +40,7 @@ export const ToolApprovalBar: React.FC<ToolApprovalBarProps> = ({
const [nowMs, setNowMs] = useState(() => Date.now());
const [showInstructionInput, setShowInstructionInput] = useState(false);
const [instruction, setInstruction] = useState('');
const [isEnablingAutoExecute, setIsEnablingAutoExecute] = useState(false);
const instructionInputRef = useRef<HTMLInputElement | null>(null);
const input = toolItem.toolCall?.input;
const hasPermissionOptions = hasAcpPermissionOptions(toolItem);
Expand Down Expand Up @@ -100,6 +106,28 @@ export const ToolApprovalBar: React.FC<ToolApprovalBarProps> = ({
onReject?.();
};

const handleEnableAutoExecute = async (event: React.MouseEvent<HTMLButtonElement>) => {
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<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
Expand Down Expand Up @@ -188,6 +216,18 @@ export const ToolApprovalBar: React.FC<ToolApprovalBarProps> = ({
>
<MessageSquareX size={13} />
</IconButton>
<span className="tool-approval-bar__divider" aria-hidden="true" />
<IconButton
className="tool-approval-bar__icon-button tool-approval-bar__auto-execute-button"
variant="default"
size="xs"
onClick={handleEnableAutoExecute}
disabled={isEnablingAutoExecute}
tooltip={t('toolCards.approval.enableAutoExecuteTooltip')}
aria-label={t('toolCards.approval.enableAutoExecute')}
>
<ShieldCheck size={13} />
</IconButton>
</>
)}
</span>
Expand Down
4 changes: 4 additions & 0 deletions src/web-ui/src/locales/en-US/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions src/web-ui/src/locales/zh-CN/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,11 @@
"confirm": "允许",
"reject": "拒绝",
"rejectWithInstruction": "说明后拒绝",
"enableAutoExecute": "开启工具自动执行",
"confirmTooltip": "允许执行这个工具",
"enableAutoExecuteTooltip": "开启工具自动执行",
"autoExecuteEnabled": "已开启工具自动执行",
"autoExecuteEnableFailed": "开启工具自动执行失败",
"rejectTooltip": "拒绝执行这个工具",
"rejectWithInstructionTooltip": "拒绝并告诉助手下一步怎么做",
"rejectInstructionLabel": "拒绝说明",
Expand Down
4 changes: 4 additions & 0 deletions src/web-ui/src/locales/zh-TW/flow-chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,11 @@
"confirm": "允許",
"reject": "拒絕",
"rejectWithInstruction": "說明後拒絕",
"enableAutoExecute": "開啟工具自動執行",
"confirmTooltip": "允許執行這個工具",
"enableAutoExecuteTooltip": "開啟工具自動執行",
"autoExecuteEnabled": "已開啟工具自動執行",
"autoExecuteEnableFailed": "開啟工具自動執行失敗",
"rejectTooltip": "拒絕執行這個工具",
"rejectWithInstructionTooltip": "拒絕並告訴助手下一步怎麼做",
"rejectInstructionLabel": "拒絕說明",
Expand Down
Loading